From 61a5352bb876ab47cd8809f71bf488abadb54c10 Mon Sep 17 00:00:00 2001 From: otsmr Date: Mon, 9 Feb 2026 23:45:34 +0100 Subject: [PATCH 1/8] switch to go_router --- android/app/src/main/AndroidManifest.xml | 1 - lib/app.dart | 45 +-- lib/src/constants/routes.keys.dart | 55 ++++ lib/src/providers/routing.provider.dart | 285 ++++++++++++++++++ lib/src/services/intent/links.intent.dart | 44 +-- lib/src/themes/dark.dart | 13 + lib/src/themes/light.dart | 10 + .../views/camera/camera_qr_scanner.view.dart | 8 +- lib/src/views/chats/chat_list.view.dart | 116 ++----- .../chat_list_components/feedback_btn.dart | 13 +- .../chat_list_components/group_list_item.dart | 75 ++--- lib/src/views/chats/chat_messages.view.dart | 24 +- .../chat_list_entry.dart | 15 +- .../message_send_state_icon.dart | 14 +- lib/src/views/chats/start_new_chat.view.dart | 24 +- .../group_context_menu.component.dart | 17 +- .../components/max_flame_list_title.dart | 12 +- .../user_context_menu.component.dart | 14 +- lib/src/views/components/verified_shield.dart | 14 +- lib/src/views/groups/group.view.dart | 60 ++-- .../group_create_select_members.view.dart | 17 +- .../views/groups/group_member.context.dart | 10 +- lib/src/views/home.view.dart | 28 +- lib/src/views/onboarding/recover.view.dart | 13 +- lib/src/views/onboarding/register.view.dart | 16 +- lib/src/views/public_profile.view.dart | 13 +- .../views/settings/backup/backup.view.dart | 14 +- ...rver.view.dart => backup_server.view.dart} | 8 +- ...ackup.view.dart => setup_backup.view.dart} | 23 +- .../settings/chat/chat_settings.view.dart | 14 +- .../views/settings/data_and_storage.view.dart | 27 +- .../settings/developer/developer.view.dart | 28 +- lib/src/views/settings/help/help.view.dart | 116 +------ lib/src/views/settings/privacy.view.dart | 14 +- ...sers.dart => privacy_view_block.view.dart} | 8 +- .../settings/profile/modify_avatar.view.dart | 8 +- .../views/settings/profile/profile.view.dart | 10 +- .../views/settings/settings_main.view.dart | 156 +--------- .../user_study_questionnaire.view.dart | 13 +- .../user_study/user_study_welcome.view.dart | 21 +- pubspec.lock | 8 + pubspec.yaml | 1 + 42 files changed, 632 insertions(+), 793 deletions(-) create mode 100644 lib/src/constants/routes.keys.dart create mode 100644 lib/src/providers/routing.provider.dart create mode 100644 lib/src/themes/dark.dart create mode 100644 lib/src/themes/light.dart rename lib/src/views/settings/backup/{twonly_safe_server.view.dart => backup_server.view.dart} (95%) rename lib/src/views/settings/backup/{twonly_safe_backup.view.dart => setup_backup.view.dart} (91%) rename lib/src/views/settings/{privacy_view_block.users.dart => privacy_view_block.view.dart} (95%) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 7f54da8..d6847c0 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -5,7 +5,6 @@ android:icon="@mipmap/ic_launcher"> with WidgetsBindingObserver { return ListenableBuilder( listenable: context.watch(), builder: (BuildContext context, Widget? child) { - return MaterialApp( + return MaterialApp.router( + routerConfig: routerProvider, scaffoldMessengerKey: globalRootScaffoldMessengerKey, - restorationScopeId: 'app', localizationsDelegates: const [ AppLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], + debugShowCheckedModeBanner: false, supportedLocales: const [ Locale('en', ''), Locale('de', ''), ], - onGenerateTitle: (BuildContext context) => 'twonly', - theme: ThemeData( - colorScheme: ColorScheme.fromSeed( - seedColor: const Color(0xFF57CC99), - ), - pageTransitionsTheme: const PageTransitionsTheme( - builders: { - TargetPlatform.android: PredictiveBackPageTransitionsBuilder(), - }, - ), - inputDecorationTheme: const InputDecorationTheme( - border: OutlineInputBorder(), - ), - ), - darkTheme: ThemeData.dark().copyWith( - colorScheme: ColorScheme.fromSeed( - brightness: Brightness.dark, - seedColor: const Color(0xFF57CC99), - surface: const Color.fromARGB(255, 20, 18, 23), - surfaceContainer: const Color.fromARGB(255, 33, 30, 39), - ), - inputDecorationTheme: const InputDecorationTheme( - border: OutlineInputBorder(), - ), - ), + title: 'twonly', + theme: lightTheme, + darkTheme: darkTheme, themeMode: context.watch().themeMode, - initialRoute: '/', - routes: { - '/': (context) => const AppMainWidget(initialPage: 1), - '/chats': (context) => const AppMainWidget(initialPage: 0), - }, ); }, ); @@ -213,7 +190,7 @@ class _AppMainWidgetState extends State { child = const DatabaseMigrationView(); } else if (_isUserCreated) { if (gUser.twonlySafeBackup == null && !_skipBackup && kReleaseMode) { - child = TwonlyIdentityBackupView( + child = SetupBackupView( callBack: () { _skipBackup = true; setState(() {}); diff --git a/lib/src/constants/routes.keys.dart b/lib/src/constants/routes.keys.dart new file mode 100644 index 0000000..aa8bab5 --- /dev/null +++ b/lib/src/constants/routes.keys.dart @@ -0,0 +1,55 @@ +class Routes { + static const String home = '/'; + static const String chats = '/chats'; + static const String chatsAddNewUser = '/chats/add_new_user'; + static const String chatsArchived = '/chats/archived'; + static const String chatsStartNewChat = '/chats/start_new_chat'; + static const String chatsCameraSendTo = '/chats/camera_send_to'; + static const String chatsMediaViewer = '/chats/media_viewer'; + static const String chatsMessages = '/chats/messages'; + + static String groupCreateSelectMember(String? groupId) => + '/group/create/select_member${groupId == null ? '' : '/$groupId'}'; + + static String profileGroup(String groupId) => '/profile/group/$groupId'; + static String profileContact(int contactId) => '/profile/contact/$contactId'; + + static const String cameraQRScanner = '/camera/qr_scanner'; + + static const String settings = '/settings'; + static const String settingsProfile = '/settings/profile'; + static const String settingsPublicProfile = '/settings/public_profile'; + static const String settingsProfileModifyAvatar = + '/settings/profile/modify_avatar'; + static const String settingsAccount = '/settings/account'; + static const String settingsSubscription = '/settings/subscription'; + static const String settingsBackup = '/settings/backup'; + static const String settingsBackupServer = '/settings/backup/server'; + static const String settingsBackupRecovery = '/settings/backup/recovery'; + static const String settingsBackupSetup = '/settings/backup/setup'; + static const String settingsAppearance = '/settings/appearance'; + static const String settingsChats = '/settings/chats'; + static const String settingsChatsReactions = '/settings/chats/reactions'; + static const String settingsPrivacy = '/settings/privacy'; + static const String settingsPrivacyBlockUsers = + '/settings/privacy/block_users'; + static const String settingsNotification = '/settings/notification'; + static const String settingsStorage = '/settings/storage_data'; + static const String settingsStorageImport = '/settings/storage_data/import'; + static const String settingsStorageExport = '/settings/storage_data/export'; + static const String settingsHelp = '/settings/help'; + static const String settingsHelpFaq = '/settings/help/faq'; + static const String settingsHelpContactUs = '/settings/help/contact_us'; + static const String settingsHelpDiagnostics = '/settings/help/diagnostics'; + static const String settingsHelpUserStudy = '/settings/help/user_study'; + static const String settingsHelpUserStudyQuestionnaire = + '/settings/help/user_study/questionnaire'; + static const String settingsHelpCredits = '/settings/help/credits'; + static const String settingsHelpChangelog = '/settings/help/changelog'; + static const String settingsDeveloper = '/settings/developer'; + static const String settingsDeveloperRetransmissionDatabase = + '/settings/developer/retransmission_database'; + static const String settingsDeveloperAutomatedTesting = + '/settings/developer/automated_testing'; + static const String settingsInvite = '/settings/invite'; +} diff --git a/lib/src/providers/routing.provider.dart b/lib/src/providers/routing.provider.dart new file mode 100644 index 0000000..44dc744 --- /dev/null +++ b/lib/src/providers/routing.provider.dart @@ -0,0 +1,285 @@ +import 'package:go_router/go_router.dart'; +import 'package:twonly/app.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; +import 'package:twonly/src/database/twonly.db.dart'; +import 'package:twonly/src/views/camera/camera_qr_scanner.view.dart'; +import 'package:twonly/src/views/camera/camera_send_to.view.dart'; +import 'package:twonly/src/views/chats/add_new_user.view.dart'; +import 'package:twonly/src/views/chats/archived_chats.view.dart'; +import 'package:twonly/src/views/chats/chat_messages.view.dart'; +import 'package:twonly/src/views/chats/media_viewer.view.dart'; +import 'package:twonly/src/views/chats/start_new_chat.view.dart'; +import 'package:twonly/src/views/contact/contact.view.dart'; +import 'package:twonly/src/views/groups/group.view.dart'; +import 'package:twonly/src/views/groups/group_create_select_members.view.dart'; +import 'package:twonly/src/views/onboarding/recover.view.dart'; +import 'package:twonly/src/views/public_profile.view.dart'; +import 'package:twonly/src/views/settings/account.view.dart'; +import 'package:twonly/src/views/settings/appearance.view.dart'; +import 'package:twonly/src/views/settings/backup/backup.view.dart'; +import 'package:twonly/src/views/settings/backup/backup_server.view.dart'; +import 'package:twonly/src/views/settings/backup/setup_backup.view.dart'; +import 'package:twonly/src/views/settings/chat/chat_reactions.view.dart'; +import 'package:twonly/src/views/settings/chat/chat_settings.view.dart'; +import 'package:twonly/src/views/settings/data_and_storage.view.dart'; +import 'package:twonly/src/views/settings/data_and_storage/export_media.view.dart'; +import 'package:twonly/src/views/settings/data_and_storage/import_media.view.dart'; +import 'package:twonly/src/views/settings/developer/automated_testing.view.dart'; +import 'package:twonly/src/views/settings/developer/developer.view.dart'; +import 'package:twonly/src/views/settings/developer/retransmission_data.view.dart'; +import 'package:twonly/src/views/settings/help/changelog.view.dart'; +import 'package:twonly/src/views/settings/help/contact_us.view.dart'; +import 'package:twonly/src/views/settings/help/credits.view.dart'; +import 'package:twonly/src/views/settings/help/diagnostics.view.dart'; +import 'package:twonly/src/views/settings/help/faq.view.dart'; +import 'package:twonly/src/views/settings/help/help.view.dart'; +import 'package:twonly/src/views/settings/notification.view.dart'; +import 'package:twonly/src/views/settings/privacy.view.dart'; +import 'package:twonly/src/views/settings/privacy_view_block.view.dart'; +import 'package:twonly/src/views/settings/profile/modify_avatar.view.dart'; +import 'package:twonly/src/views/settings/profile/profile.view.dart'; +import 'package:twonly/src/views/settings/settings_main.view.dart'; +import 'package:twonly/src/views/settings/share_with_friends.view.dart'; +import 'package:twonly/src/views/settings/subscription/subscription.view.dart'; +import 'package:twonly/src/views/user_study/user_study_questionnaire.view.dart'; +import 'package:twonly/src/views/user_study/user_study_welcome.view.dart'; + +final routerProvider = GoRouter( + routes: [ + GoRoute( + path: Routes.home, + builder: (context, state) => const AppMainWidget(initialPage: 1), + ), + + // Chats + GoRoute( + path: Routes.chats, + builder: (context, state) => const AppMainWidget(initialPage: 0), + routes: [ + GoRoute( + path: 'add_new_user', + builder: (context, state) => const AddNewUserView(), + ), + GoRoute( + path: 'archived', + builder: (context, state) => const ArchivedChatsView(), + ), + GoRoute( + path: 'start_new_chat', + builder: (context, state) => const StartNewChatView(), + ), + GoRoute( + path: 'camera_send_to', + builder: (context, state) { + final group = state.extra! as Group; + return CameraSendToView(group); + }, + ), + GoRoute( + path: 'media_viewer', + builder: (context, state) { + final group = state.extra! as Group; + return MediaViewerView(group); + }, + ), + GoRoute( + path: 'messages', + builder: (context, state) { + final group = state.extra! as Group; + return ChatMessagesView(group); + }, + ), + ], + ), + + GoRoute( + path: '/profile/contact/:contactId', + builder: (context, state) { + final contactId = state.pathParameters['contactId']!; + return ContactView(int.parse(contactId)); + }, + ), + + GoRoute( + path: '/profile/group/:groupId', + builder: (context, state) { + final groupId = state.pathParameters['groupId']!; + return GroupView(groupId); + }, + ), + + GoRoute( + path: '/group/create/select_member', + builder: (context, state) { + return const GroupCreateSelectMembersView(); + }, + routes: [ + GoRoute( + path: ':groupId', + builder: (context, state) { + final groupId = state.pathParameters['groupId']; + return GroupCreateSelectMembersView(groupId: groupId); + }, + ), + ], + ), + + GoRoute( + path: Routes.cameraQRScanner, + builder: (context, state) { + return const QrCodeScannerView(); + }, + ), + + // settings + GoRoute( + path: Routes.settings, + builder: (context, state) => const SettingsMainView(), + routes: [ + GoRoute( + path: 'profile', + builder: (context, state) => const ProfileView(), + routes: [ + GoRoute( + path: 'modify_avatar', + builder: (context, state) => const ModifyAvatarView(), + ) + ], + ), + GoRoute( + path: 'public_profile', + builder: (context, state) => const PublicProfileView(), + ), + GoRoute( + path: 'account', + builder: (context, state) => const AccountView(), + ), + GoRoute( + path: 'subscription', + builder: (context, state) => const SubscriptionView(), + ), + GoRoute( + path: 'backup', + builder: (context, state) => const BackupView(), + routes: [ + GoRoute( + path: 'server', + builder: (context, state) => const BackupServerView(), + ), + GoRoute( + path: 'recovery', + builder: (context, state) => const BackupRecoveryView(), + ), + GoRoute( + path: 'setup', + builder: (context, state) => SetupBackupView( + isPasswordChangeOnly: state.extra as bool? ?? false, + ), + ), + ], + ), + GoRoute( + path: 'appearance', + builder: (context, state) => const AppearanceView(), + ), + GoRoute( + path: 'chats', + builder: (context, state) => const ChatSettingsView(), + routes: [ + GoRoute( + path: 'reactions', + builder: (context, state) => const ChatReactionSelectionView(), + ), + ], + ), + GoRoute( + path: 'privacy', + builder: (context, state) => const PrivacyView(), + routes: [ + GoRoute( + path: 'block_users', + builder: (context, state) => const PrivacyViewBlockUsersView(), + ) + ], + ), + GoRoute( + path: 'notification', + builder: (context, state) => const NotificationView(), + ), + GoRoute( + path: 'storage_data', + builder: (context, state) => const DataAndStorageView(), + routes: [ + GoRoute( + path: 'import', + builder: (context, state) => const ImportMediaView(), + ), + GoRoute( + path: 'export', + builder: (context, state) => const ExportMediaView(), + ), + ], + ), + GoRoute( + path: 'help', + builder: (context, state) => const HelpView(), + routes: [ + GoRoute( + path: 'faq', + builder: (context, state) => const FaqView(), + ), + GoRoute( + path: 'contact_us', + builder: (context, state) => const ContactUsView(), + ), + GoRoute( + path: 'diagnostics', + builder: (context, state) => const DiagnosticsView(), + ), + GoRoute( + path: 'user_study', + builder: (context, state) => UserStudyWelcomeView( + wasOpenedAutomatic: state.extra as bool? ?? false, + ), + routes: [ + GoRoute( + path: 'questionnaire', + builder: (context, state) => + const UserStudyQuestionnaireView(), + ), + ], + ), + GoRoute( + path: 'credits', + builder: (context, state) => const CreditsView(), + ), + GoRoute( + path: 'changelog', + builder: (context, state) => ChangeLogView( + changeLog: state.extra as String?, + ), + ), + ], + ), + GoRoute( + path: 'developer', + builder: (context, state) => const DeveloperSettingsView(), + routes: [ + GoRoute( + path: 'retransmission_database', + builder: (context, state) => const RetransmissionDataView(), + ), + GoRoute( + path: 'automated_testing', + builder: (context, state) => const AutomatedTestingView(), + ), + ], + ), + GoRoute( + path: 'invite', + builder: (context, state) => const ShareWithFriendsView(), + ), + ], + ), + ], +); diff --git a/lib/src/services/intent/links.intent.dart b/lib/src/services/intent/links.intent.dart index 425d60a..03d70db 100644 --- a/lib/src/services/intent/links.intent.dart +++ b/lib/src/services/intent/links.intent.dart @@ -5,8 +5,11 @@ import 'dart:typed_data'; import 'package:collection/collection.dart'; import 'package:drift/drift.dart' show Value; import 'package:flutter/material.dart'; +import 'package:flutter_sharing_intent/flutter_sharing_intent.dart'; import 'package:flutter_sharing_intent/model/sharing_file.dart'; +import 'package:go_router/go_router.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/services/api/mediafiles/upload.service.dart'; @@ -16,8 +19,6 @@ import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/camera/share_image_editor.view.dart'; import 'package:twonly/src/views/chats/add_new_user.view.dart'; import 'package:twonly/src/views/components/alert_dialog.dart'; -import 'package:twonly/src/views/contact/contact.view.dart'; -import 'package:twonly/src/views/public_profile.view.dart'; Future handleIntentUrl(BuildContext context, Uri uri) async { if (!uri.scheme.startsWith('http')) return false; @@ -32,14 +33,7 @@ Future handleIntentUrl(BuildContext context, Uri uri) async { if (!context.mounted) return false; if (username == gUser.username) { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const PublicProfileView(); - }, - ), - ); + await context.push(Routes.settingsPublicProfile); return true; } @@ -91,14 +85,7 @@ Future handleIntentUrl(BuildContext context, Uri uri) async { ); } } else { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return ContactView(contact.userId); - }, - ), - ); + await context.push(Routes.profileContact(contact.userId)); } } else { await showAlertDialog( @@ -149,6 +136,27 @@ Future handleIntentMediaFile( ); } +StreamSubscription> initIntentStreams( + BuildContext context, + void Function(Uri) onUrlCallBack, +) { + FlutterSharingIntent.instance.getInitialSharing().then((f) { + if (!context.mounted) return; + handleIntentSharedFile(context, f, onUrlCallBack); + }); + + return FlutterSharingIntent.instance.getMediaStream().listen( + (f) { + if (!context.mounted) return; + handleIntentSharedFile(context, f, onUrlCallBack); + }, + // ignore: inference_failure_on_untyped_parameter + onError: (err) { + Log.error('getIntentDataStream error: $err'); + }, + ); +} + Future handleIntentSharedFile( BuildContext context, List files, diff --git a/lib/src/themes/dark.dart b/lib/src/themes/dark.dart new file mode 100644 index 0000000..621e0b6 --- /dev/null +++ b/lib/src/themes/dark.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +final ThemeData darkTheme = ThemeData.dark().copyWith( + colorScheme: ColorScheme.fromSeed( + brightness: Brightness.dark, + seedColor: const Color(0xFF57CC99), + surface: const Color.fromARGB(255, 20, 18, 23), + surfaceContainer: const Color.fromARGB(255, 33, 30, 39), + ), + inputDecorationTheme: const InputDecorationTheme( + border: OutlineInputBorder(), + ), +); diff --git a/lib/src/themes/light.dart b/lib/src/themes/light.dart new file mode 100644 index 0000000..88bfc71 --- /dev/null +++ b/lib/src/themes/light.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +final ThemeData lightTheme = ThemeData( + colorScheme: ColorScheme.fromSeed( + seedColor: const Color(0xFF57CC99), + ), + inputDecorationTheme: const InputDecorationTheme( + border: OutlineInputBorder(), + ), +); diff --git a/lib/src/views/camera/camera_qr_scanner.view.dart b/lib/src/views/camera/camera_qr_scanner.view.dart index 32703cc..6d6201b 100644 --- a/lib/src/views/camera/camera_qr_scanner.view.dart +++ b/lib/src/views/camera/camera_qr_scanner.view.dart @@ -4,13 +4,13 @@ import 'package:twonly/src/views/camera/camera_preview_components/camera_preview import 'package:twonly/src/views/camera/camera_preview_components/camera_preview_controller_view.dart'; import 'package:twonly/src/views/camera/camera_preview_components/main_camera_controller.dart'; -class QrCodeScanner extends StatefulWidget { - const QrCodeScanner({super.key}); +class QrCodeScannerView extends StatefulWidget { + const QrCodeScannerView({super.key}); @override - State createState() => QrCodeScannerState(); + State createState() => QrCodeScannerViewState(); } -class QrCodeScannerState extends State { +class QrCodeScannerViewState extends State { final MainCameraController _mainCameraController = MainCameraController(); @override diff --git a/lib/src/views/chats/chat_list.view.dart b/lib/src/views/chats/chat_list.view.dart index e15f2d0..d49791b 100644 --- a/lib/src/views/chats/chat_list.view.dart +++ b/lib/src/views/chats/chat_list.view.dart @@ -4,28 +4,21 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/providers/connection.provider.dart'; import 'package:twonly/src/providers/purchases.provider.dart'; import 'package:twonly/src/services/subscription.service.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/storage.dart'; -import 'package:twonly/src/views/chats/add_new_user.view.dart'; -import 'package:twonly/src/views/chats/archived_chats.view.dart'; import 'package:twonly/src/views/chats/chat_list_components/connection_info.comp.dart'; import 'package:twonly/src/views/chats/chat_list_components/feedback_btn.dart'; import 'package:twonly/src/views/chats/chat_list_components/group_list_item.dart'; -import 'package:twonly/src/views/chats/start_new_chat.view.dart'; import 'package:twonly/src/views/components/avatar_icon.component.dart'; import 'package:twonly/src/views/components/notification_badge.dart'; -import 'package:twonly/src/views/public_profile.view.dart'; -import 'package:twonly/src/views/settings/help/changelog.view.dart'; -import 'package:twonly/src/views/settings/profile/profile.view.dart'; -import 'package:twonly/src/views/settings/settings_main.view.dart'; -import 'package:twonly/src/views/settings/subscription/subscription.view.dart'; -import 'package:twonly/src/views/user_study/user_study_welcome.view.dart'; class ChatListView extends StatefulWidget { const ChatListView({super.key}); @@ -64,15 +57,9 @@ class _ChatListViewState extends State { WidgetsBinding.instance.addPostFrameCallback((_) async { if (gUser.subscriptionPlan == SubscriptionPlan.Tester.name && !gUser.askedForUserStudyPermission) { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const UserStudyWelcomeView( - wasOpenedAutomatic: true, - ); - }, - ), + await context.push( + Routes.settingsHelpUserStudy, + extra: true, ); } @@ -89,15 +76,9 @@ class _ChatListViewState extends State { // only show changelog to people who already have contacts // this prevents that this is shown directly after the user registered if (_groupsNotPinned.isNotEmpty) { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return ChangeLogView( - changeLog: changeLog, - ); - }, - ), + await context.push( + Routes.settingsHelpChangelog, + extra: changeLog, ); } } @@ -120,14 +101,7 @@ class _ChatListViewState extends State { children: [ GestureDetector( onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const ProfileView(); - }, - ), - ); + await context.push(Routes.settingsProfile); if (!mounted) return; setState(() {}); // gUser has updated }, @@ -141,16 +115,7 @@ class _ChatListViewState extends State { const Text('twonly '), if (plan != SubscriptionPlan.Free) GestureDetector( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const SubscriptionView(); - }, - ), - ); - }, + onTap: () => context.push(Routes.settingsSubscription), child: Container( decoration: BoxDecoration( color: context.color.primary, @@ -184,28 +149,15 @@ class _ChatListViewState extends State { child: IconButton( key: searchForOtherUsers, icon: const FaIcon(FontAwesomeIcons.userPlus, size: 18), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const AddNewUserView(), - ), - ); - }, + onPressed: () => context.push(Routes.chatsAddNewUser), ), ); }, ), IconButton( onPressed: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const SettingsMainView(), - ), - ); - if (!mounted) return; - setState(() {}); // gUser may has changed... + await context.push(Routes.settings); + if (mounted) setState(() {}); // gUser may has changed... }, icon: const FaIcon(FontAwesomeIcons.gear, size: 19), ), @@ -234,14 +186,7 @@ class _ChatListViewState extends State { padding: const EdgeInsets.all(10), child: OutlinedButton.icon( icon: const Icon(Icons.person_add), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const AddNewUserView(), - ), - ); - }, + onPressed: () => context.push(Routes.chatsAddNewUser), label: Text( context.lang.chatListViewSearchUserNameBtn, ), @@ -265,16 +210,7 @@ class _ChatListViewState extends State { textAlign: TextAlign.center, style: const TextStyle(fontSize: 13), ), - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const ArchivedChatsView(); - }, - ), - ); - }, + onTap: () => context.push(Routes.chatsArchived), ); } // Check if the index is for the pinned users @@ -320,16 +256,7 @@ class _ChatListViewState extends State { color: context.color.primary, child: InkWell( borderRadius: BorderRadius.circular(12), - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const PublicProfileView(); - }, - ), - ); - }, + onTap: () => context.push(Routes.settingsPublicProfile), child: SizedBox( width: 45, height: 45, @@ -345,16 +272,7 @@ class _ChatListViewState extends State { const SizedBox(height: 12), FloatingActionButton( backgroundColor: context.color.primary, - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const StartNewChatView(); - }, - ), - ); - }, + onPressed: () => context.push(Routes.chatsStartNewChat), child: FaIcon( FontAwesomeIcons.penToSquare, color: isDarkMode(context) ? Colors.black : Colors.white, diff --git a/lib/src/views/chats/chat_list_components/feedback_btn.dart b/lib/src/views/chats/chat_list_components/feedback_btn.dart index dd25ecf..412ab19 100644 --- a/lib/src/views/chats/chat_list_components/feedback_btn.dart +++ b/lib/src/views/chats/chat_list_components/feedback_btn.dart @@ -1,10 +1,10 @@ import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:go_router/go_router.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/storage.dart'; -import 'package:twonly/src/views/settings/help/contact_us.view.dart'; class FeedbackIconButton extends StatefulWidget { const FeedbackIconButton({super.key}); @@ -37,14 +37,7 @@ class _FeedbackIconButtonState extends State { } return IconButton( - onPressed: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const ContactUsView(), - ), - ); - }, + onPressed: () => context.push(Routes.settingsHelpContactUs), color: Colors.grey, tooltip: context.lang.feedbackTooltip, icon: const FaIcon(FontAwesomeIcons.commentDots, size: 19), diff --git a/lib/src/views/chats/chat_list_components/group_list_item.dart b/lib/src/views/chats/chat_list_components/group_list_item.dart index 763e389..b760097 100644 --- a/lib/src/views/chats/chat_list_components/group_list_item.dart +++ b/lib/src/views/chats/chat_list_components/group_list_item.dart @@ -1,24 +1,22 @@ import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:go_router/go_router.dart'; import 'package:mutex/mutex.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/tables/messages.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/services/api/mediafiles/download.service.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/views/camera/camera_send_to.view.dart'; import 'package:twonly/src/views/chats/chat_list_components/last_message_time.dart'; -import 'package:twonly/src/views/chats/chat_messages.view.dart'; import 'package:twonly/src/views/chats/chat_messages_components/message_send_state_icon.dart'; -import 'package:twonly/src/views/chats/media_viewer.view.dart'; import 'package:twonly/src/views/components/avatar_icon.component.dart'; import 'package:twonly/src/views/components/flame.dart'; import 'package:twonly/src/views/components/group_context_menu.component.dart'; -import 'package:twonly/src/views/contact/contact.view.dart'; -import 'package:twonly/src/views/groups/group.view.dart'; class GroupListItem extends StatefulWidget { const GroupListItem({ @@ -161,13 +159,9 @@ class _UserListItem extends State { Future onTap() async { if (_currentMessage == null && widget.group.totalMediaCounter == 0) { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return CameraSendToView(widget.group); - }, - ), + await context.push( + Routes.chatsCameraSendTo, + extra: widget.group, ); return; } @@ -185,26 +179,18 @@ class _UserListItem extends State { } if (mediaFile.downloadState! == DownloadState.ready) { if (!mounted) return; - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return MediaViewerView(widget.group); - }, - ), + await context.push( + Routes.chatsMediaViewer, + extra: widget.group, ); return; } } } if (!mounted) return; - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return ChatMessagesView(widget.group); - }, - ), + await context.push( + Routes.chatsMessages, + extra: widget.group, ); } @@ -250,42 +236,27 @@ class _UserListItem extends State { ), leading: GestureDetector( onTap: () async { - Widget pushWidget = GroupView(widget.group); - if (widget.group.isDirectChat) { final contacts = await twonlyDB.groupsDao .getGroupContact(widget.group.groupId); - pushWidget = ContactView(contacts.first.userId); + if (!context.mounted) return; + await context.push(Routes.profileContact(contacts.first.userId)); + return; + } else { + await context.push(Routes.profileGroup(widget.group.groupId)); } - if (!context.mounted) return; - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return pushWidget; - }, - ), - ); }, child: AvatarIcon(group: widget.group), ), trailing: (widget.group.leftGroup) ? null : IconButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - if (_hasNonOpenedMediaFile) { - return ChatMessagesView(widget.group); - } else { - return CameraSendToView(widget.group); - } - }, - ), - ); - }, + onPressed: () => context.push( + _hasNonOpenedMediaFile + ? Routes.chatsMessages + : Routes.chatsCameraSendTo, + extra: widget.group, + ), icon: FaIcon( _hasNonOpenedMediaFile ? FontAwesomeIcons.solidComments diff --git a/lib/src/views/chats/chat_messages.view.dart b/lib/src/views/chats/chat_messages.view.dart index 273aca8..771ac88 100644 --- a/lib/src/views/chats/chat_messages.view.dart +++ b/lib/src/views/chats/chat_messages.view.dart @@ -1,11 +1,12 @@ import 'dart:async'; import 'dart:collection'; - import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:go_router/go_router.dart'; import 'package:mutex/mutex.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/tables/messages.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; @@ -21,8 +22,6 @@ import 'package:twonly/src/views/components/avatar_icon.component.dart'; import 'package:twonly/src/views/components/blink.component.dart'; import 'package:twonly/src/views/components/flame.dart'; import 'package:twonly/src/views/components/verified_shield.dart'; -import 'package:twonly/src/views/contact/contact.view.dart'; -import 'package:twonly/src/views/groups/group.view.dart'; Color getMessageColor(Message message) { return (message.senderId == null) @@ -291,23 +290,10 @@ class _ChatMessagesViewState extends State { await twonlyDB.groupsDao.getAllGroupMembers(group.groupId); if (!context.mounted) return; if (member.isEmpty) return; - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return ContactView(member.first.contactId); - }, - ), - ); + await context + .push(Routes.profileContact(member.first.contactId)); } else { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return GroupView(group); - }, - ), - ); + await context.push(Routes.profileGroup(group.groupId)); } }, child: Row( diff --git a/lib/src/views/chats/chat_messages_components/chat_list_entry.dart b/lib/src/views/chats/chat_messages_components/chat_list_entry.dart index 44e51e3..7b05a6d 100644 --- a/lib/src/views/chats/chat_messages_components/chat_list_entry.dart +++ b/lib/src/views/chats/chat_messages_components/chat_list_entry.dart @@ -1,7 +1,9 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/tables/messages.table.dart' hide MessageActions; @@ -17,7 +19,6 @@ import 'package:twonly/src/views/chats/chat_messages_components/message_actions. import 'package:twonly/src/views/chats/chat_messages_components/message_context_menu.dart'; import 'package:twonly/src/views/chats/chat_messages_components/response_container.dart'; import 'package:twonly/src/views/components/avatar_icon.component.dart'; -import 'package:twonly/src/views/contact/contact.view.dart'; class ChatListEntry extends StatefulWidget { const ChatListEntry({ @@ -204,15 +205,9 @@ class _ChatListEntryState extends State { hideContactAvatar ? const SizedBox(width: 24) : GestureDetector( - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - ContactView(widget.message.senderId!), - ), - ); - }, + onTap: () => context.push( + Routes.profileContact(widget.message.senderId!), + ), child: AvatarIcon( contactId: widget.message.senderId, fontSize: 12, diff --git a/lib/src/views/chats/chat_messages_components/message_send_state_icon.dart b/lib/src/views/chats/chat_messages_components/message_send_state_icon.dart index a2cec19..c8400b2 100644 --- a/lib/src/views/chats/chat_messages_components/message_send_state_icon.dart +++ b/lib/src/views/chats/chat_messages_components/message_send_state_icon.dart @@ -2,13 +2,14 @@ import 'dart:collection'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:go_router/go_router.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/tables/messages.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/components/animate_icon.dart'; -import 'package:twonly/src/views/settings/subscription/subscription.view.dart'; enum MessageSendState { received, @@ -163,16 +164,7 @@ class _MessageSendStateIconState extends State { style: const TextStyle(fontSize: 9), ); - onTap = () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const SubscriptionView(); - }, - ), - ); - }; + onTap = () => context.push(Routes.settingsSubscription); } if (mediaFile.uploadState == UploadState.preprocessing || mediaFile.uploadState == UploadState.initialized) { diff --git a/lib/src/views/chats/start_new_chat.view.dart b/lib/src/views/chats/start_new_chat.view.dart index 5689112..fad23f3 100644 --- a/lib/src/views/chats/start_new_chat.view.dart +++ b/lib/src/views/chats/start_new_chat.view.dart @@ -2,17 +2,17 @@ import 'dart:async'; import 'package:drift/drift.dart' hide Column; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:go_router/go_router.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/views/chats/add_new_user.view.dart'; import 'package:twonly/src/views/chats/chat_messages.view.dart'; import 'package:twonly/src/views/components/avatar_icon.component.dart'; import 'package:twonly/src/views/components/flame.dart'; import 'package:twonly/src/views/components/group_context_menu.component.dart'; import 'package:twonly/src/views/components/user_context_menu.component.dart'; -import 'package:twonly/src/views/groups/group_create_select_members.view.dart'; class StartNewChatView extends StatefulWidget { const StartNewChatView({super.key}); @@ -165,15 +165,8 @@ class _StartNewChatView extends State { size: 13, ), ), - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - const GroupCreateSelectMembersView(), - ), - ); - }, + onTap: () => context + .push(Routes.groupCreateSelectMember(null)), ); } if (i == 1) { @@ -185,14 +178,7 @@ class _StartNewChatView extends State { size: 13, ), ), - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const AddNewUserView(), - ), - ); - }, + onTap: () => context.push(Routes.chatsAddNewUser), ); } if (i == 2) { diff --git a/lib/src/views/components/group_context_menu.component.dart b/lib/src/views/components/group_context_menu.component.dart index 438a77d..ce856f4 100644 --- a/lib/src/views/components/group_context_menu.component.dart +++ b/lib/src/views/components/group_context_menu.component.dart @@ -1,10 +1,11 @@ import 'package:drift/drift.dart' hide Column; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:go_router/go_router.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/views/chats/chat_messages.view.dart'; import 'package:twonly/src/views/components/alert_dialog.dart'; import 'package:twonly/src/views/components/context_menu.component.dart'; @@ -45,16 +46,10 @@ class GroupContextMenu extends StatelessWidget { ), ContextMenuItem( title: context.lang.contextMenuOpenChat, - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return ChatMessagesView(group); - }, - ), - ); - }, + onTap: () => context.push( + Routes.chatsMessages, + extra: group, + ), icon: FontAwesomeIcons.comments, ), if (!group.archived) diff --git a/lib/src/views/components/max_flame_list_title.dart b/lib/src/views/components/max_flame_list_title.dart index 5029bbb..adddb1f 100644 --- a/lib/src/views/components/max_flame_list_title.dart +++ b/lib/src/views/components/max_flame_list_title.dart @@ -2,7 +2,9 @@ import 'dart:async'; import 'package:clock/clock.dart'; import 'package:drift/drift.dart' show Value; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/services/flame.service.dart'; import 'package:twonly/src/services/subscription.service.dart'; @@ -10,7 +12,6 @@ import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/components/animate_icon.dart'; import 'package:twonly/src/views/components/better_list_title.dart'; -import 'package:twonly/src/views/settings/subscription/subscription.view.dart'; class MaxFlameListTitle extends StatefulWidget { const MaxFlameListTitle({ @@ -46,14 +47,7 @@ class _MaxFlameListTitleState extends State { Future _restoreFlames() async { if (!isPayingUser(getCurrentPlan())) { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const SubscriptionView(); - }, - ), - ); + await context.push(Routes.settingsSubscription); return; } Log.info( diff --git a/lib/src/views/components/user_context_menu.component.dart b/lib/src/views/components/user_context_menu.component.dart index 7151848..6471bd9 100644 --- a/lib/src/views/components/user_context_menu.component.dart +++ b/lib/src/views/components/user_context_menu.component.dart @@ -1,9 +1,10 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:go_router/go_router.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/components/context_menu.component.dart'; -import 'package:twonly/src/views/contact/contact.view.dart'; class UserContextMenu extends StatelessWidget { const UserContextMenu({ @@ -20,16 +21,7 @@ class UserContextMenu extends StatelessWidget { items: [ ContextMenuItem( title: context.lang.contextMenuUserProfile, - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return ContactView(contact.userId); - }, - ), - ); - }, + onTap: () => context.push(Routes.profileContact(contact.userId)), icon: FontAwesomeIcons.user, ), ], diff --git a/lib/src/views/components/verified_shield.dart b/lib/src/views/components/verified_shield.dart index 94c4389..73b6ab2 100644 --- a/lib/src/views/components/verified_shield.dart +++ b/lib/src/views/components/verified_shield.dart @@ -1,9 +1,10 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:go_router/go_router.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/views/public_profile.view.dart'; class VerifiedShield extends StatefulWidget { const VerifiedShield({ @@ -58,16 +59,7 @@ class _VerifiedShieldState extends State { return GestureDetector( onTap: (contact == null) ? null - : () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const PublicProfileView(); - }, - ), - ); - }, + : () => context.push(Routes.settingsPublicProfile), child: Tooltip( message: isVerified ? 'You verified this contact' diff --git a/lib/src/views/groups/group.view.dart b/lib/src/views/groups/group.view.dart index 918068b..2279dc0 100644 --- a/lib/src/views/groups/group.view.dart +++ b/lib/src/views/groups/group.view.dart @@ -19,16 +19,16 @@ import 'package:twonly/src/views/groups/group_member.context.dart'; import 'package:twonly/src/views/settings/profile/profile.view.dart'; class GroupView extends StatefulWidget { - const GroupView(this.group, {super.key}); + const GroupView(this.groupId, {super.key}); - final Group group; + final String groupId; @override State createState() => _GroupViewState(); } class _GroupViewState extends State { - late Group group; + Group? _group; List<(Contact, GroupMember)> members = []; @@ -37,7 +37,6 @@ class _GroupViewState extends State { @override void initState() { - group = widget.group; initAsync(); super.initState(); } @@ -50,16 +49,15 @@ class _GroupViewState extends State { } Future initAsync() async { - final groupStream = twonlyDB.groupsDao.watchGroup(widget.group.groupId); + final groupStream = twonlyDB.groupsDao.watchGroup(widget.groupId); groupSub = groupStream.listen((update) { if (update != null) { setState(() { - group = update; + _group = update; }); } }); - final membersStream = - twonlyDB.groupsDao.watchGroupMembers(widget.group.groupId); + final membersStream = twonlyDB.groupsDao.watchGroupMembers(widget.groupId); membersSub = membersStream.listen((update) { setState(() { members = update; @@ -71,13 +69,13 @@ class _GroupViewState extends State { } Future _updateGroupName() async { - final newGroupName = await showGroupNameChangeDialog(context, group); + final newGroupName = await showGroupNameChangeDialog(context, _group!); if (context.mounted && newGroupName != null && newGroupName != '' && - newGroupName != group.groupName) { - if (!await updateGroupName(group, newGroupName)) { + newGroupName != _group!.groupName) { + if (!await updateGroupName(_group!, newGroupName)) { if (mounted) { showNetworkIssue(context); } @@ -89,11 +87,13 @@ class _GroupViewState extends State { final selectedUserIds = await Navigator.push( context, MaterialPageRoute( - builder: (context) => GroupCreateSelectMembersView(group: group), + builder: (context) => GroupCreateSelectMembersView( + groupId: _group?.groupId, + ), ), ) as List?; if (selectedUserIds == null) return; - if (!await addNewGroupMembers(group, selectedUserIds)) { + if (!await addNewGroupMembers(_group!, selectedUserIds)) { if (mounted) { showNetworkIssue(context); } @@ -115,7 +115,7 @@ class _GroupViewState extends State { if (members.isNotEmpty) { // In case there are other members, check that there is at least one other admin before I leave the group. - if (group.isGroupAdmin) { + if (_group!.isGroupAdmin) { if (!members.any((m) => m.$2.memberState == MemberState.admin)) { if (!mounted) return; await showAlertDialog( @@ -131,16 +131,17 @@ class _GroupViewState extends State { late bool success; - if (group.isGroupAdmin) { + if (_group!.isGroupAdmin) { // Current user is a admin, to the state can be updated by the user him self. - final keyPair = IdentityKeyPair.fromSerialized(group.myGroupPrivateKey!); + final keyPair = + IdentityKeyPair.fromSerialized(_group!.myGroupPrivateKey!); success = await removeMemberFromGroup( - group, + _group!, keyPair.getPublicKey().serialize(), gUser.userId, ); } else { - success = await leaveAsNonAdminFromGroup(group); + success = await leaveAsNonAdminFromGroup(_group!); } if (!success) { @@ -153,6 +154,9 @@ class _GroupViewState extends State { @override Widget build(BuildContext context) { + if (_group == null) { + return Container(); + } return Scaffold( appBar: AppBar( title: const Text(''), @@ -162,7 +166,7 @@ class _GroupViewState extends State { Padding( padding: const EdgeInsets.all(10), child: AvatarIcon( - group: group, + group: _group, fontSize: 30, ), ), @@ -171,24 +175,24 @@ class _GroupViewState extends State { children: [ Padding( padding: const EdgeInsets.only(right: 10), - child: VerifiedShield(key: Key(group.groupId), group: group), + child: VerifiedShield(key: Key(_group!.groupId), group: _group), ), Text( - substringBy(group.groupName, 25), + substringBy(_group!.groupName, 25), style: const TextStyle(fontSize: 20), ), ], ), const SizedBox(height: 50), - if (group.isGroupAdmin && !group.leftGroup) + if (_group!.isGroupAdmin && !_group!.leftGroup) BetterListTile( icon: FontAwesomeIcons.pencil, text: context.lang.groupNameInput, onTap: _updateGroupName, ), SelectChatDeletionTimeListTitle( - groupId: widget.group.groupId, - disabled: !group.isGroupAdmin, + groupId: widget.groupId, + disabled: !_group!.isGroupAdmin, ), const Divider(), ListTile( @@ -203,7 +207,7 @@ class _GroupViewState extends State { ), ), ), - if (group.isGroupAdmin && !group.leftGroup) + if (_group!.isGroupAdmin && !_group!.leftGroup) BetterListTile( icon: FontAwesomeIcons.plus, text: context.lang.addMember, @@ -216,7 +220,7 @@ class _GroupViewState extends State { fontSize: 16, ), text: context.lang.you, - trailing: (group.isGroupAdmin) ? Text(context.lang.admin) : null, + trailing: (_group!.isGroupAdmin) ? Text(context.lang.admin) : null, onTap: () async { await Navigator.push( context, @@ -229,7 +233,7 @@ class _GroupViewState extends State { ...members.map((member) { return GroupMemberContextMenu( key: ValueKey(member.$1.userId), - group: group, + group: _group!, contact: member.$1, member: member.$2, child: BetterListTile( @@ -256,7 +260,7 @@ class _GroupViewState extends State { const SizedBox(height: 10), const Divider(), const SizedBox(height: 10), - if (!group.leftGroup) + if (!_group!.leftGroup) BetterListTile( icon: FontAwesomeIcons.rightFromBracket, color: Colors.red, diff --git a/lib/src/views/groups/group_create_select_members.view.dart b/lib/src/views/groups/group_create_select_members.view.dart index 4306c86..c4dfe71 100644 --- a/lib/src/views/groups/group_create_select_members.view.dart +++ b/lib/src/views/groups/group_create_select_members.view.dart @@ -12,8 +12,8 @@ import 'package:twonly/src/views/components/user_context_menu.component.dart'; import 'package:twonly/src/views/groups/group_create_select_group_name.view.dart'; class GroupCreateSelectMembersView extends StatefulWidget { - const GroupCreateSelectMembersView({this.group, super.key}); - final Group? group; + const GroupCreateSelectMembersView({this.groupId, super.key}); + final String? groupId; @override State createState() => _StartNewChatView(); } @@ -46,9 +46,8 @@ class _StartNewChatView extends State { } Future initAsync() async { - if (widget.group != null) { - final members = - await twonlyDB.groupsDao.getGroupContact(widget.group!.groupId); + if (widget.groupId != null) { + final members = await twonlyDB.groupsDao.getGroupContact(widget.groupId!); for (final member in members) { alreadyInGroup.add(member.userId); } @@ -101,7 +100,7 @@ class _StartNewChatView extends State { } Future submitChanges() async { - if (widget.group != null) { + if (widget.groupId != null) { Navigator.pop(context, selectedUsers.toList()); return; } @@ -125,7 +124,7 @@ class _StartNewChatView extends State { child: Scaffold( appBar: AppBar( title: Text( - widget.group == null + widget.groupId == null ? context.lang.selectMembers : context.lang.addMember, ), @@ -133,7 +132,9 @@ class _StartNewChatView extends State { floatingActionButton: FilledButton.icon( onPressed: selectedUsers.isEmpty ? null : submitChanges, label: Text( - widget.group == null ? context.lang.next : context.lang.updateGroup, + widget.groupId == null + ? context.lang.next + : context.lang.updateGroup, ), icon: const FaIcon(FontAwesomeIcons.penToSquare), ), diff --git a/lib/src/views/groups/group_member.context.dart b/lib/src/views/groups/group_member.context.dart index 687401f..543e0c8 100644 --- a/lib/src/views/groups/group_member.context.dart +++ b/lib/src/views/groups/group_member.context.dart @@ -1,7 +1,9 @@ import 'package:drift/drift.dart' show Value; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:go_router/go_router.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/tables/groups.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; @@ -9,7 +11,6 @@ import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; import 'package:twonly/src/services/api/messages.dart'; import 'package:twonly/src/services/group.services.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/views/chats/chat_messages.view.dart'; import 'package:twonly/src/views/components/alert_dialog.dart'; import 'package:twonly/src/views/components/context_menu.component.dart'; import 'package:twonly/src/views/groups/group.view.dart'; @@ -128,12 +129,7 @@ class GroupMemberContextMenu extends StatelessWidget { return; } if (!context.mounted) return; - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ChatMessagesView(directChat), - ), - ); + await context.push(Routes.chatsMessages, extra: directChat); }, icon: FontAwesomeIcons.message, ), diff --git a/lib/src/views/home.view.dart b/lib/src/views/home.view.dart index b4e5069..09d40e9 100644 --- a/lib/src/views/home.view.dart +++ b/lib/src/views/home.view.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:app_links/app_links.dart'; import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; -import 'package:flutter_sharing_intent/flutter_sharing_intent.dart'; import 'package:flutter_sharing_intent/model/sharing_file.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:twonly/globals.dart'; @@ -126,31 +125,10 @@ class HomeViewState extends State { } }); - _intentStreamSub = FlutterSharingIntent.instance.getMediaStream().listen( - (f) { - if (mounted) { - handleIntentSharedFile( - context, - f, - _mainCameraController.setSharedLinkForPreview, - ); - } - }, - // ignore: inference_failure_on_untyped_parameter - onError: (err) { - Log.error('getIntentDataStream error: $err'); - }, + _intentStreamSub = initIntentStreams( + context, + _mainCameraController.setSharedLinkForPreview, ); - - FlutterSharingIntent.instance.getInitialSharing().then((f) { - if (mounted) { - handleIntentSharedFile( - context, - f, - _mainCameraController.setSharedLinkForPreview, - ); - } - }); } @override diff --git a/lib/src/views/onboarding/recover.view.dart b/lib/src/views/onboarding/recover.view.dart index e9b061c..5b9fc6b 100644 --- a/lib/src/views/onboarding/recover.view.dart +++ b/lib/src/views/onboarding/recover.view.dart @@ -1,12 +1,13 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:go_router/go_router.dart'; import 'package:restart_app/restart_app.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/model/json/userdata.dart'; import 'package:twonly/src/services/twonly_safe/restore.twonly_safe.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/components/alert_dialog.dart'; -import 'package:twonly/src/views/settings/backup/twonly_safe_server.view.dart'; class BackupRecoveryView extends StatefulWidget { const BackupRecoveryView({super.key}); @@ -135,14 +136,8 @@ class _BackupRecoveryViewState extends State { Center( child: OutlinedButton( onPressed: () async { - backupServer = await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const TwonlySafeServerView(); - }, - ), - ); + backupServer = + await context.push(Routes.settingsBackupServer); setState(() {}); }, child: Text(context.lang.backupExpertSettings), diff --git a/lib/src/views/onboarding/register.view.dart b/lib/src/views/onboarding/register.view.dart index 32a932b..7da4a58 100644 --- a/lib/src/views/onboarding/register.view.dart +++ b/lib/src/views/onboarding/register.view.dart @@ -2,11 +2,12 @@ import 'dart:async'; import 'dart:convert'; - import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:go_router/go_router.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/constants/secure_storage_keys.dart'; import 'package:twonly/src/model/json/userdata.dart'; import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart'; @@ -17,7 +18,6 @@ import 'package:twonly/src/utils/pow.dart'; import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/views/components/alert_dialog.dart'; import 'package:twonly/src/views/groups/group.view.dart'; -import 'package:twonly/src/views/onboarding/recover.view.dart'; class RegisterView extends StatefulWidget { const RegisterView({ @@ -302,16 +302,8 @@ class _RegisterViewState extends State { mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ OutlinedButton.icon( - onPressed: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const BackupRecoveryView(); - }, - ), - ); - }, + onPressed: () => + context.push(Routes.settingsBackupRecovery), label: Text(context.lang.twonlySafeRecoverBtn), ), ], diff --git a/lib/src/views/public_profile.view.dart b/lib/src/views/public_profile.view.dart index 3fc405e..e3a937b 100644 --- a/lib/src/views/public_profile.view.dart +++ b/lib/src/views/public_profile.view.dart @@ -1,15 +1,15 @@ import 'dart:convert'; import 'dart:typed_data'; - import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:go_router/go_router.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:share_plus/share_plus.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/utils/avatars.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/qr.dart'; -import 'package:twonly/src/views/camera/camera_qr_scanner.view.dart'; import 'package:twonly/src/views/components/better_list_title.dart'; class PublicProfileView extends StatefulWidget { @@ -96,14 +96,7 @@ class _PublicProfileViewState extends State { BetterListTile( leading: const FaIcon(FontAwesomeIcons.qrcode), text: context.lang.scanOtherProfile, - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const QrCodeScanner(), - ), - ); - }, + onTap: () => context.push(Routes.cameraQRScanner), ), BetterListTile( leading: const FaIcon( diff --git a/lib/src/views/settings/backup/backup.view.dart b/lib/src/views/settings/backup/backup.view.dart index 1720925..f2fc158 100644 --- a/lib/src/views/settings/backup/backup.view.dart +++ b/lib/src/views/settings/backup/backup.view.dart @@ -1,11 +1,12 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:go_router/go_router.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/model/json/userdata.dart'; import 'package:twonly/src/services/twonly_safe/create_backup.twonly_safe.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/views/settings/backup/twonly_safe_backup.view.dart'; void Function() gUpdateBackupView = () {}; @@ -60,16 +61,7 @@ class _BackupViewState extends State { } Future changeTwonlySafePassword() async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const TwonlyIdentityBackupView( - isPasswordChangeOnly: true, - ); - }, - ), - ); + await context.push(Routes.settingsBackupSetup, extra: true); setState(() { // gUser was updated }); diff --git a/lib/src/views/settings/backup/twonly_safe_server.view.dart b/lib/src/views/settings/backup/backup_server.view.dart similarity index 95% rename from lib/src/views/settings/backup/twonly_safe_server.view.dart rename to lib/src/views/settings/backup/backup_server.view.dart index c0e4bfd..871ed6e 100644 --- a/lib/src/views/settings/backup/twonly_safe_server.view.dart +++ b/lib/src/views/settings/backup/backup_server.view.dart @@ -11,14 +11,14 @@ import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/storage.dart'; -class TwonlySafeServerView extends StatefulWidget { - const TwonlySafeServerView({super.key}); +class BackupServerView extends StatefulWidget { + const BackupServerView({super.key}); @override - State createState() => _TwonlySafeServerViewState(); + State createState() => _BackupServerViewState(); } -class _TwonlySafeServerViewState extends State { +class _BackupServerViewState extends State { final TextEditingController _urlController = TextEditingController(); final TextEditingController _usernameController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); diff --git a/lib/src/views/settings/backup/twonly_safe_backup.view.dart b/lib/src/views/settings/backup/setup_backup.view.dart similarity index 91% rename from lib/src/views/settings/backup/twonly_safe_backup.view.dart rename to lib/src/views/settings/backup/setup_backup.view.dart index 3162fde..eb9d242 100644 --- a/lib/src/views/settings/backup/twonly_safe_backup.view.dart +++ b/lib/src/views/settings/backup/setup_backup.view.dart @@ -2,13 +2,14 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show rootBundle; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:go_router/go_router.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/services/twonly_safe/common.twonly_safe.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/components/alert_dialog.dart'; -import 'package:twonly/src/views/settings/backup/twonly_safe_server.view.dart'; -class TwonlyIdentityBackupView extends StatefulWidget { - const TwonlyIdentityBackupView({ +class SetupBackupView extends StatefulWidget { + const SetupBackupView({ this.isPasswordChangeOnly = false, this.callBack, super.key, @@ -20,11 +21,10 @@ class TwonlyIdentityBackupView extends StatefulWidget { final bool isPasswordChangeOnly; @override - State createState() => - _TwonlyIdentityBackupViewState(); + State createState() => _SetupBackupViewState(); } -class _TwonlyIdentityBackupViewState extends State { +class _SetupBackupViewState extends State { bool obscureText = true; bool isLoading = false; final TextEditingController passwordCtrl = TextEditingController(); @@ -179,16 +179,7 @@ class _TwonlyIdentityBackupViewState extends State { const SizedBox(height: 10), Center( child: OutlinedButton( - onPressed: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const TwonlySafeServerView(); - }, - ), - ); - }, + onPressed: () => context.push(Routes.settingsBackupServer), child: Text(context.lang.backupExpertSettings), ), ), diff --git a/lib/src/views/settings/chat/chat_settings.view.dart b/lib/src/views/settings/chat/chat_settings.view.dart index 1a20c27..db3667b 100644 --- a/lib/src/views/settings/chat/chat_settings.view.dart +++ b/lib/src/views/settings/chat/chat_settings.view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/views/settings/chat/chat_reactions.view.dart'; class ChatSettingsView extends StatefulWidget { const ChatSettingsView({super.key}); @@ -25,16 +26,7 @@ class _ChatSettingsViewState extends State { children: [ ListTile( title: Text(context.lang.settingsPreSelectedReactions), - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const ChatReactionSelectionView(); - }, - ), - ); - }, + onTap: () => context.push(Routes.settingsChatsReactions), ), ], ), diff --git a/lib/src/views/settings/data_and_storage.view.dart b/lib/src/views/settings/data_and_storage.view.dart index 5ff3161..1088a27 100644 --- a/lib/src/views/settings/data_and_storage.view.dart +++ b/lib/src/views/settings/data_and_storage.view.dart @@ -1,14 +1,13 @@ import 'dart:async'; import 'dart:io'; - import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/services/api/mediafiles/download.service.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/storage.dart'; -import 'package:twonly/src/views/settings/data_and_storage/export_media.view.dart'; -import 'package:twonly/src/views/settings/data_and_storage/import_media.view.dart'; class DataAndStorageView extends StatefulWidget { const DataAndStorageView({super.key}); @@ -91,32 +90,14 @@ class _DataAndStorageViewState extends State { title: Text( context.lang.exportMemories, ), - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (_) { - return const ExportMediaView(); - }, - ), - ); - }, + onTap: () => context.push(Routes.settingsStorageExport), ), if (Platform.isAndroid) ListTile( title: Text( context.lang.importMemories, ), - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (_) { - return const ImportMediaView(); - }, - ), - ); - }, + onTap: () => context.push(Routes.settingsStorageImport), ), const Divider(), ListTile( diff --git a/lib/src/views/settings/developer/developer.view.dart b/lib/src/views/settings/developer/developer.view.dart index 1dcc1d8..37413cf 100644 --- a/lib/src/views/settings/developer/developer.view.dart +++ b/lib/src/views/settings/developer/developer.view.dart @@ -1,12 +1,12 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:restart_app/restart_app.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/views/components/alert_dialog.dart'; -import 'package:twonly/src/views/settings/developer/automated_testing.view.dart'; -import 'package:twonly/src/views/settings/developer/retransmission_data.view.dart'; class DeveloperSettingsView extends StatefulWidget { const DeveloperSettingsView({super.key}); @@ -55,16 +55,8 @@ class _DeveloperSettingsViewState extends State { ), ListTile( title: const Text('Show Retransmission Database'), - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const RetransmissionDataView(); - }, - ), - ); - }, + onTap: () => + context.push(Routes.settingsDeveloperRetransmissionDatabase), ), ListTile( title: const Text('Delete all (!) app data'), @@ -97,16 +89,8 @@ class _DeveloperSettingsViewState extends State { if (!kReleaseMode) ListTile( title: const Text('Automated Testing'), - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const AutomatedTestingView(); - }, - ), - ); - }, + onTap: () => + context.push(Routes.settingsDeveloperAutomatedTesting), ), ], ), diff --git a/lib/src/views/settings/help/help.view.dart b/lib/src/views/settings/help/help.view.dart index 89b3dd2..00d6c97 100644 --- a/lib/src/views/settings/help/help.view.dart +++ b/lib/src/views/settings/help/help.view.dart @@ -1,17 +1,13 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:go_router/go_router.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/views/components/alert_dialog.dart'; -import 'package:twonly/src/views/settings/help/changelog.view.dart'; -import 'package:twonly/src/views/settings/help/contact_us.view.dart'; -import 'package:twonly/src/views/settings/help/credits.view.dart'; -import 'package:twonly/src/views/settings/help/diagnostics.view.dart'; -import 'package:twonly/src/views/settings/help/faq.view.dart'; -import 'package:twonly/src/views/user_study/user_study_welcome.view.dart'; import 'package:url_launcher/url_launcher.dart'; class HelpView extends StatefulWidget { @@ -40,50 +36,12 @@ class _HelpViewState extends State { children: [ ListTile( title: Text(context.lang.settingsHelpFAQ), - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const FaqView(); - }, - ), - ); - }, + onTap: () => context.push(Routes.settingsHelpFaq), ), ListTile( title: Text(context.lang.settingsHelpContactUs), - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const ContactUsView(); - }, - ), - ); - }, + onTap: () => context.push(Routes.settingsHelpContactUs), ), - // ListTile( - // title: Text(context.lang.settingsResetTutorials), - // subtitle: Text( - // context.lang.settingsResetTutorialsDesc, - // style: const TextStyle(fontSize: 12), - // ), - // onTap: () async { - // await updateUserdata((user) { - // user.tutorialDisplayed = []; - // return user; - // }); - // if (!context.mounted) return; - // ScaffoldMessenger.of(context).showSnackBar( - // SnackBar( - // content: Text(context.lang.settingsResetTutorialsSuccess), - // duration: const Duration(seconds: 3), - // ), - // ); - // }, - // ), const Divider(), ListTile( title: Text(context.lang.allowErrorTracking), @@ -99,32 +57,13 @@ class _HelpViewState extends State { ), ListTile( title: Text(context.lang.settingsHelpDiagnostics), - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const DiagnosticsView(); - }, - ), - ); - }, + onTap: () => context.push(Routes.settingsHelpDiagnostics), ), const Divider(), if (gUser.userStudyParticipantsToken == null || kDebugMode) ListTile( title: const Text('Teilnahme an Nutzerstudie'), - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const UserStudyWelcomeView(); - }, - ), - ); - setState(() {}); // gUser has changed - }, + onTap: () => context.push(Routes.settingsHelpUserStudy), ), FutureBuilder( future: PackageInfo.fromPlatform(), @@ -141,59 +80,34 @@ class _HelpViewState extends State { ), ListTile( title: Text(context.lang.settingsHelpLicenses), - onTap: () { - showLicensePage(context: context); - }, + onTap: () => showLicensePage(context: context), ), ListTile( title: Text(context.lang.settingsHelpCredits), - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const CreditsView(); - }, - ), - ); - }, + onTap: () => context.push(Routes.settingsHelpCredits), ), ListTile( title: const Text('Changelog'), - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const ChangeLogView(); - }, - ), - ); - }, + onTap: () => context.push(Routes.settingsHelpChangelog), ), ListTile( title: const Text('Open Source'), - onTap: () async { - await launchUrl( - Uri.parse('https://github.com/twonlyapp/twonly-app'), - ); - }, + onTap: () => launchUrl( + Uri.parse('https://github.com/twonlyapp/twonly-app'), + ), trailing: const FaIcon(FontAwesomeIcons.arrowUpRightFromSquare, size: 15), ), ListTile( title: Text(context.lang.settingsHelpImprint), - onTap: () async { - await launchUrl(Uri.parse('https://twonly.eu/de/legal/')); - }, + onTap: () => launchUrl(Uri.parse('https://twonly.eu/de/legal/')), trailing: const FaIcon(FontAwesomeIcons.arrowUpRightFromSquare, size: 15), ), ListTile( title: Text(context.lang.settingsHelpTerms), - onTap: () async { - await launchUrl(Uri.parse('https://twonly.eu/de/legal/agb.html')); - }, + onTap: () => + launchUrl(Uri.parse('https://twonly.eu/de/legal/agb.html')), trailing: const FaIcon(FontAwesomeIcons.arrowUpRightFromSquare, size: 15), ), diff --git a/lib/src/views/settings/privacy.view.dart b/lib/src/views/settings/privacy.view.dart index c3c50c3..5e006c4 100644 --- a/lib/src/views/settings/privacy.view.dart +++ b/lib/src/views/settings/privacy.view.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/views/settings/privacy_view_block.users.dart'; class PrivacyView extends StatefulWidget { const PrivacyView({super.key}); @@ -40,16 +41,7 @@ class _PrivacyViewState extends State { } }, ), - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const PrivacyViewBlockUsers(); - }, - ), - ); - }, + onTap: () => context.push(Routes.settingsPrivacyBlockUsers), ), ], ), diff --git a/lib/src/views/settings/privacy_view_block.users.dart b/lib/src/views/settings/privacy_view_block.view.dart similarity index 95% rename from lib/src/views/settings/privacy_view_block.users.dart rename to lib/src/views/settings/privacy_view_block.view.dart index e0038c3..dda1aa1 100644 --- a/lib/src/views/settings/privacy_view_block.users.dart +++ b/lib/src/views/settings/privacy_view_block.view.dart @@ -7,14 +7,14 @@ import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/components/avatar_icon.component.dart'; import 'package:twonly/src/views/components/user_context_menu.component.dart'; -class PrivacyViewBlockUsers extends StatefulWidget { - const PrivacyViewBlockUsers({super.key}); +class PrivacyViewBlockUsersView extends StatefulWidget { + const PrivacyViewBlockUsersView({super.key}); @override - State createState() => _PrivacyViewBlockUsers(); + State createState() => _PrivacyViewBlockUsers(); } -class _PrivacyViewBlockUsers extends State { +class _PrivacyViewBlockUsers extends State { late Stream> allUsers; List filteredUsers = []; String filter = ''; diff --git a/lib/src/views/settings/profile/modify_avatar.view.dart b/lib/src/views/settings/profile/modify_avatar.view.dart index fd95b5c..1ba50c0 100644 --- a/lib/src/views/settings/profile/modify_avatar.view.dart +++ b/lib/src/views/settings/profile/modify_avatar.view.dart @@ -6,14 +6,14 @@ import 'package:twonly/globals.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/storage.dart'; -class ModifyAvatar extends StatefulWidget { - const ModifyAvatar({super.key}); +class ModifyAvatarView extends StatefulWidget { + const ModifyAvatarView({super.key}); @override - State createState() => _ModifyAvatarState(); + State createState() => _ModifyAvatarViewState(); } -class _ModifyAvatarState extends State { +class _ModifyAvatarViewState extends State { final AvatarMakerController _avatarMakerController = PersistentAvatarMakerController(customizedPropertyCategories: []); diff --git a/lib/src/views/settings/profile/profile.view.dart b/lib/src/views/settings/profile/profile.view.dart index 61de089..e7b0b56 100644 --- a/lib/src/views/settings/profile/profile.view.dart +++ b/lib/src/views/settings/profile/profile.view.dart @@ -3,7 +3,9 @@ import 'package:avatar_maker/avatar_maker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:go_router/go_router.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart'; import 'package:twonly/src/services/twonly_safe/common.twonly_safe.dart'; import 'package:twonly/src/services/twonly_safe/create_backup.twonly_safe.dart'; @@ -11,7 +13,6 @@ import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/views/components/better_list_title.dart'; import 'package:twonly/src/views/groups/group.view.dart'; -import 'package:twonly/src/views/settings/profile/modify_avatar.view.dart'; class ProfileView extends StatefulWidget { const ProfileView({super.key}); @@ -110,12 +111,7 @@ class _ProfileViewState extends State { icon: const Icon(Icons.edit), label: Text(context.lang.settingsProfileCustomizeAvatar), onPressed: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const ModifyAvatar(), - ), - ); + await context.push(Routes.settingsProfileModifyAvatar); await _avatarMakerController.performRestore(); setState(() {}); }, diff --git a/lib/src/views/settings/settings_main.view.dart b/lib/src/views/settings/settings_main.view.dart index 7a25ede..a8b353f 100644 --- a/lib/src/views/settings/settings_main.view.dart +++ b/lib/src/views/settings/settings_main.view.dart @@ -1,23 +1,12 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:go_router/go_router.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/components/avatar_icon.component.dart'; import 'package:twonly/src/views/components/better_list_title.dart'; -import 'package:twonly/src/views/public_profile.view.dart'; -import 'package:twonly/src/views/settings/account.view.dart'; -import 'package:twonly/src/views/settings/appearance.view.dart'; -import 'package:twonly/src/views/settings/backup/backup.view.dart'; -import 'package:twonly/src/views/settings/chat/chat_settings.view.dart'; -import 'package:twonly/src/views/settings/data_and_storage.view.dart'; -import 'package:twonly/src/views/settings/developer/developer.view.dart'; -import 'package:twonly/src/views/settings/help/help.view.dart'; -import 'package:twonly/src/views/settings/notification.view.dart'; -import 'package:twonly/src/views/settings/privacy.view.dart'; -import 'package:twonly/src/views/settings/profile/profile.view.dart'; -import 'package:twonly/src/views/settings/share_with_friends.view.dart'; -import 'package:twonly/src/views/settings/subscription/subscription.view.dart'; class SettingsMainView extends StatefulWidget { const SettingsMainView({super.key}); @@ -42,14 +31,7 @@ class _SettingsMainViewState extends State { Expanded( child: GestureDetector( onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const ProfileView(); - }, - ), - ); + await context.push(Routes.settingsProfile); setState(() {}); }, child: ColoredBox( @@ -86,16 +68,7 @@ class _SettingsMainViewState extends State { Align( alignment: Alignment.centerRight, child: IconButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const PublicProfileView(); - }, - ), - ); - }, + onPressed: () => context.push(Routes.settingsPublicProfile), icon: const FaIcon(FontAwesomeIcons.qrcode), ), ), @@ -105,160 +78,61 @@ class _SettingsMainViewState extends State { BetterListTile( icon: FontAwesomeIcons.user, text: context.lang.settingsAccount, - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const AccountView(); - }, - ), - ); - }, + onTap: () => context.push(Routes.settingsAccount), ), BetterListTile( icon: FontAwesomeIcons.shieldHeart, text: context.lang.settingsSubscription, - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const SubscriptionView(); - }, - ), - ); - }, + onTap: () => context.push(Routes.settingsSubscription), ), BetterListTile( icon: Icons.lock_clock_rounded, text: context.lang.settingsBackup, - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const BackupView(); - }, - ), - ); - }, + onTap: () => context.push(Routes.settingsBackup), ), const Divider(), BetterListTile( icon: FontAwesomeIcons.sun, text: context.lang.settingsAppearance, - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const AppearanceView(); - }, - ), - ); - }, + onTap: () => context.push(Routes.settingsAppearance), ), BetterListTile( icon: FontAwesomeIcons.comment, text: context.lang.settingsChats, - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const ChatSettingsView(); - }, - ), - ); - }, + onTap: () => context.push(Routes.settingsChats), ), BetterListTile( icon: FontAwesomeIcons.lock, text: context.lang.settingsPrivacy, - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const PrivacyView(); - }, - ), - ); - }, + onTap: () => context.push(Routes.settingsPrivacy), ), BetterListTile( icon: FontAwesomeIcons.bell, text: context.lang.settingsNotification, - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const NotificationView(); - }, - ), - ); - }, + onTap: () => context.push(Routes.settingsNotification), ), BetterListTile( icon: FontAwesomeIcons.chartPie, iconSize: 15, text: context.lang.settingsStorageData, - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const DataAndStorageView(); - }, - ), - ); - }, + onTap: () => context.push(Routes.settingsStorage), ), const Divider(), BetterListTile( icon: FontAwesomeIcons.circleQuestion, text: context.lang.settingsHelp, - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const HelpView(); - }, - ), - ); - }, + onTap: () => context.push(Routes.settingsHelp), ), if (gUser.isDeveloper) BetterListTile( icon: FontAwesomeIcons.code, text: 'Developer Settings', - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const DeveloperSettingsView(); - }, - ), - ); - }, + onTap: () => context.push(Routes.settingsDeveloper), ), BetterListTile( icon: FontAwesomeIcons.shareFromSquare, text: context.lang.inviteFriends, - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const ShareWithFriendsView(); - }, - ), - ); - }, + onTap: () => context.push(Routes.settingsInvite), ), ], ), diff --git a/lib/src/views/user_study/user_study_questionnaire.view.dart b/lib/src/views/user_study/user_study_questionnaire.view.dart index 09469be..c23a4da 100644 --- a/lib/src/views/user_study/user_study_questionnaire.view.dart +++ b/lib/src/views/user_study/user_study_questionnaire.view.dart @@ -2,19 +2,22 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:twonly/src/utils/keyvalue.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/views/user_study/user_study_data_collection.dart'; -class UserStudyQuestionnaire extends StatefulWidget { - const UserStudyQuestionnaire({super.key}); +class UserStudyQuestionnaireView extends StatefulWidget { + const UserStudyQuestionnaireView({super.key}); @override - State createState() => _UserStudyQuestionnaireState(); + State createState() => + _UserStudyQuestionnaireViewState(); } -class _UserStudyQuestionnaireState extends State { +class _UserStudyQuestionnaireViewState + extends State { final Map _responses = { 'age': null, 'education': null, @@ -62,7 +65,7 @@ class _UserStudyQuestionnaireState extends State { const SnackBar(content: Text('Vielen Dank für deine Teilnahme!')), ); - Navigator.pop(context); + context.pop(); } @override diff --git a/lib/src/views/user_study/user_study_welcome.view.dart b/lib/src/views/user_study/user_study_welcome.view.dart index ed8adb1..c11e12c 100644 --- a/lib/src/views/user_study/user_study_welcome.view.dart +++ b/lib/src/views/user_study/user_study_welcome.view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/utils/storage.dart'; -import 'package:twonly/src/views/user_study/user_study_questionnaire.view.dart'; class UserStudyWelcomeView extends StatefulWidget { const UserStudyWelcomeView({super.key, this.wasOpenedAutomatic = false}); @@ -54,16 +55,8 @@ class _UserStudyWelcomeViewState extends State { const SizedBox(height: 40), Center( child: FilledButton( - onPressed: () { - Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) { - return const UserStudyQuestionnaire(); - }, - ), - ); - }, + onPressed: () => context + .pushReplacement(Routes.settingsHelpUserStudyQuestionnaire), child: const Padding( padding: EdgeInsets.symmetric(horizontal: 30, vertical: 15), child: Text( @@ -77,9 +70,7 @@ class _UserStudyWelcomeViewState extends State { if (widget.wasOpenedAutomatic) Center( child: OutlinedButton( - onPressed: () { - Navigator.pop(context); - }, + onPressed: () => context.pop(), child: const Padding( padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), child: Text( @@ -98,7 +89,7 @@ class _UserStudyWelcomeViewState extends State { u.askedForUserStudyPermission = true; return u; }); - if (context.mounted) Navigator.pop(context); + if (context.mounted) context.pop(); }, child: const Text( 'Nicht mehr anzeigen', diff --git a/pubspec.lock b/pubspec.lock index 1227333..d9c8ce6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -852,6 +852,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3" + go_router: + dependency: "direct main" + description: + name: go_router + sha256: "7974313e217a7771557add6ff2238acb63f635317c35fa590d348fb238f00896" + url: "https://pub.dev" + source: hosted + version: "17.1.0" google_mlkit_barcode_scanning: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 72fb8fd..1a48e4e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,6 +39,7 @@ dependencies: vector_graphics: ^1.1.19 video_player: ^2.10.1 in_app_purchase: ^3.2.3 + go_router: ^17.1.0 # Trusted publisher fluttercommunity.dev From 58dd5bb122c46769e3d9db9159188f3157d2ee5b Mon Sep 17 00:00:00 2001 From: otsmr Date: Thu, 12 Feb 2026 21:54:23 +0100 Subject: [PATCH 2/8] fix: logging issues --- lib/src/services/api.service.dart | 5 ++--- lib/src/services/api/messages.dart | 2 +- lib/src/services/fcm.service.dart | 2 +- lib/src/utils/log.dart | 28 +++++++++++++++++----------- 4 files changed, 21 insertions(+), 16 deletions(-) diff --git a/lib/src/services/api.service.dart b/lib/src/services/api.service.dart index 7c3c395..b7f39c3 100644 --- a/lib/src/services/api.service.dart +++ b/lib/src/services/api.service.dart @@ -189,8 +189,7 @@ class ApiService { bool get isConnected => _channel != null && _channel!.closeCode != null; Future _onDone() async { - Log.info('websocket closed without error'); - _reconnectionDelay = 60 * 2; // the server closed the connection... + _reconnectionDelay = 3; await onClosed(); } @@ -408,7 +407,7 @@ class ApiService { } if (result.isError) { if (result.error != ErrorCode.AuthTokenNotValid) { - Log.error('got error while authenticating to the server', result); + Log.error('got error while authenticating to the server: $result'); return false; } } diff --git a/lib/src/services/api/messages.dart b/lib/src/services/api/messages.dart index ea3bafc..c92bdab 100644 --- a/lib/src/services/api/messages.dart +++ b/lib/src/services/api/messages.dart @@ -89,7 +89,7 @@ Future<(Uint8List, Uint8List?)?> tryToSendCompleteMessage({ return null; } - Log.info('Uploading $receiptId (Message to ${receipt.contactId})'); + Log.info('Uploading $receiptId'); final message = pb.Message.fromBuffer(receipt.message) ..receiptId = receiptId; diff --git a/lib/src/services/fcm.service.dart b/lib/src/services/fcm.service.dart index 433dbc8..5457b76 100644 --- a/lib/src/services/fcm.service.dart +++ b/lib/src/services/fcm.service.dart @@ -109,7 +109,7 @@ Future initFCMService() async { @pragma('vm:entry-point') Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { initLogger(); - Log.info('Handling a background message: ${message.messageId}'); + // Log.info('Handling a background message: ${message.messageId}'); await handleRemoteMessage(message); // make sure every thing run... await Future.delayed(const Duration(milliseconds: 2000)); diff --git a/lib/src/utils/log.dart b/lib/src/utils/log.dart index 1f8677a..6ac0f1b 100644 --- a/lib/src/utils/log.dart +++ b/lib/src/utils/log.dart @@ -2,7 +2,6 @@ import 'dart:io'; import 'package:clock/clock.dart'; import 'package:flutter/foundation.dart'; import 'package:logging/logging.dart'; -import 'package:mutex/mutex.dart'; import 'package:path_provider/path_provider.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:twonly/globals.dart'; @@ -66,8 +65,6 @@ class Log { } } -Mutex writeToLogGuard = Mutex(); - Future loadLogFile() async { final directory = await getApplicationSupportDirectory(); final logFile = File('${directory.path}/app.log'); @@ -95,14 +92,23 @@ Future _writeLogToFile(LogRecord record) async { logFile.createSync(recursive: true); } - // Prepare the log message final logMessage = '${clock.now().toString().split(".")[0]} ${record.level.name} [twonly] ${record.loggerName} > ${record.message}\n'; - await writeToLogGuard.protect(() async { - // Append the log message to the file - await logFile.writeAsString(logMessage, mode: FileMode.append); - }); + final raf = await logFile.open(mode: FileMode.writeOnlyAppend); + + try { + // Use FileLock.blockingExclusive to wait until the lock is available + await raf.lock(FileLock.blockingExclusive); + await raf.writeString(logMessage); + await raf.flush(); + } catch (e) { + // ignore: avoid_print + print('Error during file access: $e'); + } finally { + await raf.unlock(); + await raf.close(); + } } Future cleanLogFile() async { @@ -112,10 +118,10 @@ Future cleanLogFile() async { if (logFile.existsSync()) { final lines = await logFile.readAsLines(); - if (lines.length <= 5000) return; + if (lines.length <= 10000) return; - final removeCount = lines.length - 5000; - final remaining = lines.sublist(removeCount); + final removeCount = lines.length - 10000; + final remaining = lines.sublist(removeCount, lines.length); final sink = logFile.openWrite()..writeAll(remaining, '\n'); await sink.close(); From 0f16ab68b75d3fee0d57b6e5e7d05adfe4e666ca Mon Sep 17 00:00:00 2001 From: otsmr Date: Thu, 12 Feb 2026 21:54:42 +0100 Subject: [PATCH 3/8] fix: overwriting thumbnail --- lib/src/services/mediafiles/thumbnail.service.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/src/services/mediafiles/thumbnail.service.dart b/lib/src/services/mediafiles/thumbnail.service.dart index df7397c..53275b3 100644 --- a/lib/src/services/mediafiles/thumbnail.service.dart +++ b/lib/src/services/mediafiles/thumbnail.service.dart @@ -9,8 +9,12 @@ Future createThumbnailsForVideo( ) async { final stopwatch = Stopwatch()..start(); + if (destinationFile.existsSync()) { + return; + } + final command = - '-i "${sourceFile.path}" -ss 00:00:00 -vframes 1 -vf "scale=iw:ih:flags=lanczos" -c:v libwebp -q:v 100 -compression_level 6 "${destinationFile.path}"'; + '-y -i "${sourceFile.path}" -ss 00:00:00 -vframes 1 -vf "scale=iw:ih:flags=lanczos" -c:v libwebp -q:v 100 -compression_level 6 "${destinationFile.path}"'; final session = await FFmpegKit.execute(command); final returnCode = await session.getReturnCode(); From e6160990febd979b021358acaeb26a3ed2df818b Mon Sep 17 00:00:00 2001 From: otsmr Date: Thu, 12 Feb 2026 21:54:53 +0100 Subject: [PATCH 4/8] fix: null pointer --- .../services/api/client2client/contact.c2c.dart | 14 ++++++++------ lib/src/services/api/client2client/media.c2c.dart | 15 ++++++++++++--- .../services/api/mediafiles/download.service.dart | 4 +--- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/lib/src/services/api/client2client/contact.c2c.dart b/lib/src/services/api/client2client/contact.c2c.dart index 64d4450..83233a1 100644 --- a/lib/src/services/api/client2client/contact.c2c.dart +++ b/lib/src/services/api/client2client/contact.c2c.dart @@ -71,12 +71,14 @@ Future handleContactRequest( final contact = await twonlyDB.contactsDao .getContactByUserId(fromUserId) .getSingleOrNull(); - await twonlyDB.groupsDao.createNewDirectChat( - fromUserId, - GroupsCompanion( - groupName: Value(getContactDisplayName(contact!)), - ), - ); + if (contact != null) { + await twonlyDB.groupsDao.createNewDirectChat( + fromUserId, + GroupsCompanion( + groupName: Value(getContactDisplayName(contact)), + ), + ); + } case EncryptedContent_ContactRequest_Type.REJECT: Log.info('Got a contact reject from $fromUserId'); await twonlyDB.contactsDao.updateContact( diff --git a/lib/src/services/api/client2client/media.c2c.dart b/lib/src/services/api/client2client/media.c2c.dart index 6795acd..b99799e 100644 --- a/lib/src/services/api/client2client/media.c2c.dart +++ b/lib/src/services/api/client2client/media.c2c.dart @@ -139,12 +139,21 @@ Future handleMediaUpdate( .getMessageById(mediaUpdate.targetMessageId) .getSingleOrNull(); if (message == null) { - Log.error( - 'Got media update to message ${mediaUpdate.targetMessageId} but message not found.', + // this can happen, in case the message was already deleted. + Log.info( + 'Got media update to message ${mediaUpdate.targetMessageId} but message not found.', ); + return; + } + if (message.mediaId == null) { + // this can happen, in case the message was already deleted. + Log.warn( + 'Got media update for message ${mediaUpdate.targetMessageId} which does not have a mediaId defined.', + ); + return; } final mediaFile = - await twonlyDB.mediaFilesDao.getMediaFileById(message!.mediaId!); + await twonlyDB.mediaFilesDao.getMediaFileById(message.mediaId!); if (mediaFile == null) { Log.info( 'Got media file update, but media file was not found ${message.mediaId}', diff --git a/lib/src/services/api/mediafiles/download.service.dart b/lib/src/services/api/mediafiles/download.service.dart index a2a7802..724da2c 100644 --- a/lib/src/services/api/mediafiles/download.service.dart +++ b/lib/src/services/api/mediafiles/download.service.dart @@ -231,9 +231,7 @@ Future downloadFileFast( await handleEncryptedFile(media.mediaId); return; } else { - if (response.statusCode == 404 || - response.statusCode == 403 || - response.statusCode == 400) { + if (response.statusCode == 404 || response.statusCode == 403) { Log.error( 'Got ${response.statusCode} from server. Requesting upload again', ); From b59687c5ca591ff5472a38dee82aa41ab561bf32 Mon Sep 17 00:00:00 2001 From: otsmr Date: Thu, 12 Feb 2026 23:04:17 +0100 Subject: [PATCH 5/8] fix retransmission of media files + update dependencies --- android/settings.gradle | 2 +- dependencies | 2 +- ios/Podfile.lock | 209 +++++----- lib/app.dart | 10 +- lib/globals.dart | 5 +- lib/main.dart | 9 + lib/src/database/daos/mediafiles.dao.dart | 8 + lib/src/database/daos/signal.dao.dart | 12 +- lib/src/providers/routing.provider.dart | 4 +- lib/src/services/api.service.dart | 5 +- .../services/api/client2client/media.c2c.dart | 22 +- .../api/mediafiles/download.service.dart | 18 + .../api/mediafiles/upload.service.dart | 8 +- lib/src/services/api/server_messages.dart | 5 + .../share_image_contact_selection.view.dart | 10 +- .../best_friends_selector.dart | 5 +- .../select_show_time.dart | 2 +- .../views/camera/share_image_editor.view.dart | 9 +- .../chat_reaction_row.dart | 2 +- .../message_context_menu.dart | 6 +- .../reaction_buttons.component.dart | 2 +- lib/src/views/chats/message_info.view.dart | 2 +- lib/src/views/chats/start_new_chat.view.dart | 2 +- lib/src/views/components/alert_dialog.dart | 2 +- .../components/context_menu.component.dart | 2 +- .../select_chat_deletion_time.comp.dart | 6 +- lib/src/views/contact/contact.view.dart | 4 +- lib/src/views/groups/group.view.dart | 2 +- .../group_create_select_group_name.view.dart | 2 +- .../group_create_select_members.view.dart | 4 +- lib/src/views/home.view.dart | 6 +- .../memories/memories_photo_slider.view.dart | 5 +- lib/src/views/settings/appearance.view.dart | 8 +- .../views/settings/backup/backup.view.dart | 2 +- .../views/settings/data_and_storage.view.dart | 6 +- .../developer/automated_testing.view.dart | 2 +- .../views/settings/help/contact_us.view.dart | 8 +- .../settings/privacy_view_block.view.dart | 4 +- .../settings/profile/modify_avatar.view.dart | 4 +- .../views/settings/profile/profile.view.dart | 2 +- .../select_additional_users.view.dart | 4 +- .../select_payment.view.dart | 2 +- .../subscription_custom/voucher.view.dart | 8 +- .../user_study_questionnaire.view.dart | 4 +- pubspec.lock | 370 ++++++++++-------- test/widget_test.dart | 2 +- 46 files changed, 455 insertions(+), 363 deletions(-) diff --git a/android/settings.gradle b/android/settings.gradle index cc0a7b6..c79517d 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -18,7 +18,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version '8.9.0' apply false + id "com.android.application" version '8.9.1' apply false // START: FlutterFire Configuration id "com.google.gms.google-services" version "4.3.15" apply false // END: FlutterFire Configuration diff --git a/dependencies b/dependencies index 3a3a7e5..33111ed 160000 --- a/dependencies +++ b/dependencies @@ -1 +1 @@ -Subproject commit 3a3a7e5a6323da5413e3dd8c21abfa7cbe1c3a6f +Subproject commit 33111edeb285db34edeb9fd21762825babe71ab0 diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b0cfa92..3b9e45f 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -54,55 +54,55 @@ PODS: - file_picker (0.0.1): - DKImagePickerController/PhotoGallery - Flutter - - Firebase (12.6.0): - - Firebase/Core (= 12.6.0) - - Firebase/Core (12.6.0): + - Firebase (12.8.0): + - Firebase/Core (= 12.8.0) + - Firebase/Core (12.8.0): - Firebase/CoreOnly - - FirebaseAnalytics (~> 12.6.0) - - Firebase/CoreOnly (12.6.0): - - FirebaseCore (~> 12.6.0) - - Firebase/Messaging (12.6.0): + - FirebaseAnalytics (~> 12.8.0) + - Firebase/CoreOnly (12.8.0): + - FirebaseCore (~> 12.8.0) + - Firebase/Messaging (12.8.0): - Firebase/CoreOnly - - FirebaseMessaging (~> 12.6.0) - - firebase_core (4.3.0): - - Firebase/CoreOnly (= 12.6.0) + - FirebaseMessaging (~> 12.8.0) + - firebase_core (4.4.0): + - Firebase/CoreOnly (= 12.8.0) - Flutter - - firebase_messaging (16.1.0): - - Firebase/Messaging (= 12.6.0) + - firebase_messaging (16.1.1): + - Firebase/Messaging (= 12.8.0) - firebase_core - Flutter - - FirebaseAnalytics (12.6.0): - - FirebaseAnalytics/Default (= 12.6.0) - - FirebaseCore (~> 12.6.0) - - FirebaseInstallations (~> 12.6.0) + - FirebaseAnalytics (12.8.0): + - FirebaseAnalytics/Default (= 12.8.0) + - FirebaseCore (~> 12.8.0) + - FirebaseInstallations (~> 12.8.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.1) - GoogleUtilities/MethodSwizzler (~> 8.1) - GoogleUtilities/Network (~> 8.1) - "GoogleUtilities/NSData+zlib (~> 8.1)" - nanopb (~> 3.30910.0) - - FirebaseAnalytics/Default (12.6.0): - - FirebaseCore (~> 12.6.0) - - FirebaseInstallations (~> 12.6.0) - - GoogleAppMeasurement/Default (= 12.6.0) + - FirebaseAnalytics/Default (12.8.0): + - FirebaseCore (~> 12.8.0) + - FirebaseInstallations (~> 12.8.0) + - GoogleAppMeasurement/Default (= 12.8.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.1) - GoogleUtilities/MethodSwizzler (~> 8.1) - GoogleUtilities/Network (~> 8.1) - "GoogleUtilities/NSData+zlib (~> 8.1)" - nanopb (~> 3.30910.0) - - FirebaseCore (12.6.0): - - FirebaseCoreInternal (~> 12.6.0) + - FirebaseCore (12.8.0): + - FirebaseCoreInternal (~> 12.8.0) - GoogleUtilities/Environment (~> 8.1) - GoogleUtilities/Logger (~> 8.1) - - FirebaseCoreInternal (12.6.0): + - FirebaseCoreInternal (12.8.0): - "GoogleUtilities/NSData+zlib (~> 8.1)" - - FirebaseInstallations (12.6.0): - - FirebaseCore (~> 12.6.0) + - FirebaseInstallations (12.8.0): + - FirebaseCore (~> 12.8.0) - GoogleUtilities/Environment (~> 8.1) - GoogleUtilities/UserDefaults (~> 8.1) - PromisesObjC (~> 2.4) - - FirebaseMessaging (12.6.0): - - FirebaseCore (~> 12.6.0) - - FirebaseInstallations (~> 12.6.0) + - FirebaseMessaging (12.8.0): + - FirebaseCore (~> 12.8.0) + - FirebaseInstallations (~> 12.8.0) - GoogleDataTransport (~> 10.1) - GoogleUtilities/AppDelegateSwizzler (~> 8.1) - GoogleUtilities/Environment (~> 8.1) @@ -129,39 +129,39 @@ PODS: - gal (1.0.0): - Flutter - FlutterMacOS - - google_mlkit_barcode_scanning (0.14.1): + - google_mlkit_barcode_scanning (0.14.2): - Flutter - google_mlkit_commons - - GoogleMLKit/BarcodeScanning (~> 7.0.0) - - google_mlkit_commons (0.11.0): + - GoogleMLKit/BarcodeScanning (~> 9.0.0) + - google_mlkit_commons (0.11.1): - Flutter - - MLKitVision - - google_mlkit_face_detection (0.13.1): + - MLKitVision (~> 10.0.0) + - google_mlkit_face_detection (0.13.2): - Flutter - google_mlkit_commons - - GoogleMLKit/FaceDetection (~> 7.0.0) + - GoogleMLKit/FaceDetection (~> 9.0.0) - GoogleAdsOnDeviceConversion (3.2.0): - GoogleUtilities/Environment (~> 8.1) - GoogleUtilities/Logger (~> 8.1) - GoogleUtilities/Network (~> 8.1) - nanopb (~> 3.30910.0) - - GoogleAppMeasurement/Core (12.6.0): + - GoogleAppMeasurement/Core (12.8.0): - GoogleUtilities/AppDelegateSwizzler (~> 8.1) - GoogleUtilities/MethodSwizzler (~> 8.1) - GoogleUtilities/Network (~> 8.1) - "GoogleUtilities/NSData+zlib (~> 8.1)" - nanopb (~> 3.30910.0) - - GoogleAppMeasurement/Default (12.6.0): + - GoogleAppMeasurement/Default (12.8.0): - GoogleAdsOnDeviceConversion (~> 3.2.0) - - GoogleAppMeasurement/Core (= 12.6.0) - - GoogleAppMeasurement/IdentitySupport (= 12.6.0) + - GoogleAppMeasurement/Core (= 12.8.0) + - GoogleAppMeasurement/IdentitySupport (= 12.8.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.1) - GoogleUtilities/MethodSwizzler (~> 8.1) - GoogleUtilities/Network (~> 8.1) - "GoogleUtilities/NSData+zlib (~> 8.1)" - nanopb (~> 3.30910.0) - - GoogleAppMeasurement/IdentitySupport (12.6.0): - - GoogleAppMeasurement/Core (= 12.6.0) + - GoogleAppMeasurement/IdentitySupport (12.8.0): + - GoogleAppMeasurement/Core (= 12.8.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.1) - GoogleUtilities/MethodSwizzler (~> 8.1) - GoogleUtilities/Network (~> 8.1) @@ -170,14 +170,14 @@ PODS: - GoogleDataTransport (10.1.0): - nanopb (~> 3.30910.0) - PromisesObjC (~> 2.4) - - GoogleMLKit/BarcodeScanning (7.0.0): + - GoogleMLKit/BarcodeScanning (9.0.0): - GoogleMLKit/MLKitCore - - MLKitBarcodeScanning (~> 6.0.0) - - GoogleMLKit/FaceDetection (7.0.0): + - MLKitBarcodeScanning (~> 8.0.0) + - GoogleMLKit/FaceDetection (9.0.0): - GoogleMLKit/MLKitCore - - MLKitFaceDetection (~> 6.0.0) - - GoogleMLKit/MLKitCore (7.0.0): - - MLKitCommon (~> 12.0.0) + - MLKitFaceDetection (~> 8.0.0) + - GoogleMLKit/MLKitCore (9.0.0): + - MLKitCommon (~> 14.0.0) - GoogleToolboxForMac/Defines (4.2.1) - GoogleToolboxForMac/Logger (4.2.1): - GoogleToolboxForMac/Defines (= 4.2.1) @@ -247,26 +247,26 @@ PODS: - Mantle (2.2.0): - Mantle/extobjc (= 2.2.0) - Mantle/extobjc (2.2.0) - - MLImage (1.0.0-beta6) - - MLKitBarcodeScanning (6.0.0): - - MLKitCommon (~> 12.0) - - MLKitVision (~> 8.0) - - MLKitCommon (12.0.0): + - MLImage (1.0.0-beta8) + - MLKitBarcodeScanning (8.0.0): + - MLKitCommon (~> 14.0) + - MLKitVision (~> 10.0) + - MLKitCommon (14.0.0): - GoogleDataTransport (~> 10.0) - GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1) - "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)" - GoogleUtilities/Logger (~> 8.0) - GoogleUtilities/UserDefaults (~> 8.0) - GTMSessionFetcher/Core (< 4.0, >= 3.3.2) - - MLKitFaceDetection (6.0.0): - - MLKitCommon (~> 12.0) - - MLKitVision (~> 8.0) - - MLKitVision (8.0.0): + - MLKitFaceDetection (8.0.0): + - MLKitCommon (~> 14.0) + - MLKitVision (~> 10.0) + - MLKitVision (10.0.0): - GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1) - "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)" - GTMSessionFetcher/Core (< 4.0, >= 3.3.2) - - MLImage (= 1.0.0-beta6) - - MLKitCommon (~> 12.0) + - MLImage (= 1.0.0-beta8) + - MLKitCommon (~> 14.0) - nanopb (3.30910.0): - nanopb/decode (= 3.30910.0) - nanopb/encode (= 3.30910.0) @@ -274,28 +274,21 @@ PODS: - nanopb/encode (3.30910.0) - no_screenshot (0.3.2-beta.3): - Flutter - - ScreenProtectorKit (~> 1.3.1) - - objective_c (0.0.1): - - Flutter - package_info_plus (0.4.5): - Flutter - - path_provider_foundation (0.0.1): - - Flutter - - FlutterMacOS - permission_handler_apple (9.3.0): - Flutter - PromisesObjC (2.4.0) - restart_app (0.0.1): - Flutter - - ScreenProtectorKit (1.3.1) - - SDWebImage (5.21.5): - - SDWebImage/Core (= 5.21.5) - - SDWebImage/Core (5.21.5) + - SDWebImage (5.21.6): + - SDWebImage/Core (= 5.21.6) + - SDWebImage/Core (5.21.6) - SDWebImageWebPCoder (0.15.0): - libwebp (~> 1.0) - SDWebImage/Core (~> 5.17) - Sentry/HybridSDK (8.56.2) - - sentry_flutter (9.8.0): + - sentry_flutter (9.13.0): - Flutter - FlutterMacOS - Sentry/HybridSDK (= 8.56.2) @@ -307,32 +300,32 @@ PODS: - sqflite_darwin (0.0.4): - Flutter - FlutterMacOS - - sqlite3 (3.50.4): - - sqlite3/common (= 3.50.4) - - sqlite3/common (3.50.4) - - sqlite3/dbstatvtab (3.50.4): + - sqlite3 (3.51.1): + - sqlite3/common (= 3.51.1) + - sqlite3/common (3.51.1) + - sqlite3/dbstatvtab (3.51.1): - sqlite3/common - - sqlite3/fts5 (3.50.4): + - sqlite3/fts5 (3.51.1): - sqlite3/common - - sqlite3/math (3.50.4): + - sqlite3/math (3.51.1): - sqlite3/common - - sqlite3/perf-threadsafe (3.50.4): + - sqlite3/perf-threadsafe (3.51.1): - sqlite3/common - - sqlite3/rtree (3.50.4): + - sqlite3/rtree (3.51.1): - sqlite3/common - - sqlite3/session (3.50.4): + - sqlite3/session (3.51.1): - sqlite3/common - sqlite3_flutter_libs (0.0.1): - Flutter - FlutterMacOS - - sqlite3 (~> 3.50.4) + - sqlite3 (~> 3.51.1) - sqlite3/dbstatvtab - sqlite3/fts5 - sqlite3/math - sqlite3/perf-threadsafe - sqlite3/rtree - sqlite3/session - - SwiftProtobuf (1.33.3) + - SwiftProtobuf (1.34.1) - SwiftyGif (5.4.5) - url_launcher_ios (0.0.1): - Flutter @@ -373,9 +366,7 @@ DEPENDENCIES: - in_app_purchase_storekit (from `.symlinks/plugins/in_app_purchase_storekit/darwin`) - local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`) - no_screenshot (from `.symlinks/plugins/no_screenshot/ios`) - - objective_c (from `.symlinks/plugins/objective_c/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - restart_app (from `.symlinks/plugins/restart_app/ios`) - sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`) @@ -413,7 +404,6 @@ SPEC REPOS: - MLKitVision - nanopb - PromisesObjC - - ScreenProtectorKit - SDWebImage - SDWebImageWebPCoder - Sentry @@ -476,12 +466,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/local_auth_darwin/darwin" no_screenshot: :path: ".symlinks/plugins/no_screenshot/ios" - objective_c: - :path: ".symlinks/plugins/objective_c/ios" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" - path_provider_foundation: - :path: ".symlinks/plugins/path_provider_foundation/darwin" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" restart_app: @@ -514,14 +500,14 @@ SPEC CHECKSUMS: emoji_picker_flutter: ece213fc274bdddefb77d502d33080dc54e616cc ffmpeg_kit_flutter_new: 12426a19f10ac81186c67c6ebc4717f8f4364b7f file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be - Firebase: a451a7b61536298fd5cbfe3a746fd40443a50679 - firebase_core: ba00a168e719694f38960502ceb560285603d073 - firebase_messaging: bf0e29321927edc02a563c984dbfa5b063864b15 - FirebaseAnalytics: d0a97a0db6425e5a5d966340b87f92ca7b13a557 - FirebaseCore: 0e38ad5d62d980a47a64b8e9301ffa311457be04 - FirebaseCoreInternal: 69bf1306a05b8ac43004f6cc1f804bb7b05b229e - FirebaseInstallations: 631b38da2e11a83daa4bfb482f79d286a5dfa7ad - FirebaseMessaging: a61bc42dcab3f7a346d94bbb54dab2c9435b18b2 + Firebase: 9a58fdbc9d8655ed7b79a19cf9690bb007d3d46d + firebase_core: ee30637e6744af8e0c12a6a1e8a9718506ec2398 + firebase_messaging: 343de01a8d3e18b60df0c6d37f7174c44ae38e02 + FirebaseAnalytics: f20bbad8cb7f65d8a5eaefeb424ae8800a31bdfc + FirebaseCore: 0dbad74bda10b8fb9ca34ad8f375fb9dd3ebef7c + FirebaseCoreInternal: fe5fa466aeb314787093a7dce9f0beeaad5a2a21 + FirebaseInstallations: 6a14ab3d694ebd9f839c48d330da5547e9ca9dc0 + FirebaseMessaging: 7f42cfd10ec64181db4e01b305a613791c8e782c Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 flutter_image_compress_common: 1697a328fd72bfb335507c6bca1a65fa5ad87df1 flutter_keyboard_visibility_temp_fork: 95b2d534bacf6ac62e7fcbe5c2a9e2c2a17ce06f @@ -530,13 +516,13 @@ SPEC CHECKSUMS: flutter_sharing_intent: 0c1e53949f09fa8df8ac2268505687bde8ff264c flutter_volume_controller: c2be490cb0487e8b88d0d9fc2b7e1c139a4ebccb gal: baecd024ebfd13c441269ca7404792a7152fde89 - google_mlkit_barcode_scanning: 8f5987f244a43fe1167689c548342a5174108159 - google_mlkit_commons: 2abe6a70e1824e431d16a51085cb475b672c8aab - google_mlkit_face_detection: 754da2113a1952f063c7c5dc347ac6ae8934fb77 + google_mlkit_barcode_scanning: 12d8422d8f7b00726dedf9cac00188a2b98750c2 + google_mlkit_commons: a5e4ffae5bc59ea4c7b9025dc72cb6cb79dc1166 + google_mlkit_face_detection: ee4b72cfae062b4c972204be955d83055a4bfd36 GoogleAdsOnDeviceConversion: d68c69dd9581a0f5da02617b6f377e5be483970f - GoogleAppMeasurement: 3bf40aff49a601af5da1c3345702fcb4991d35ee + GoogleAppMeasurement: 72c9a682fec6290327ea5e3c4b829b247fcb2c17 GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 - GoogleMLKit: eff9e23ec1d90ea4157a1ee2e32a4f610c5b3318 + GoogleMLKit: b1eee21a41c57704fe72483b15c85cb2c0cd7444 GoogleToolboxForMac: d1a2cbf009c453f4d6ded37c105e2f67a32206d8 GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6 @@ -545,30 +531,27 @@ SPEC CHECKSUMS: libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d - MLImage: 0ad1c5f50edd027672d8b26b0fee78a8b4a0fc56 - MLKitBarcodeScanning: 0a3064da0a7f49ac24ceb3cb46a5bc67496facd2 - MLKitCommon: 07c2c33ae5640e5380beaaa6e4b9c249a205542d - MLKitFaceDetection: 2a593db4837db503ad3426b565e7aab045cefea5 - MLKitVision: 45e79d68845a2de77e2dd4d7f07947f0ed157b0e + MLImage: 0de5c6c2bf9e93b80ef752e2797f0836f03b58c0 + MLKitBarcodeScanning: 39de223e7b1b8a8fbf10816a536dd292d8a39343 + MLKitCommon: 47d47b50a031d00db62f1b0efe5a1d8b09a3b2e6 + MLKitFaceDetection: 32549f1e70e6e7731261bf9cea2b74095e2531cb + MLKitVision: 39a5a812db83c4a0794445088e567f3631c11961 nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 - no_screenshot: 89e778ede9f1e39cc3fb9404d782a42712f2a0b2 - objective_c: 89e720c30d716b036faf9c9684022048eee1eee2 + no_screenshot: 5e345998c43ffcad5d6834f249590483fcc037bd package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 - path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 restart_app: 9cda5378aacc5000e3f66ee76a9201534e7d3ecf - ScreenProtectorKit: 83a6281b02c7a5902ee6eac4f5045f674e902ae4 - SDWebImage: e9c98383c7572d713c1a0d7dd2783b10599b9838 + SDWebImage: 1bb6a1b84b6fe87b972a102bdc77dd589df33477 SDWebImageWebPCoder: 0e06e365080397465cc73a7a9b472d8a3bd0f377 Sentry: b53951377b78e21a734f5dc8318e333dbfc682d7 - sentry_flutter: 4c33648b7e83310aa1fdb1b10c5491027d9643f0 + sentry_flutter: dbed9a62ae39716b685a80140705c330d200d941 share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 - sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b - sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1 - SwiftProtobuf: e1b437c8e31a4c5577b643249a0bb62ed4f02153 + sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b + sqlite3_flutter_libs: d13b8b3003f18f596e542bcb9482d105577eff41 + SwiftProtobuf: c901f00a3e125dc33cac9b16824da85682ee47da SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a diff --git a/lib/app.dart b/lib/app.dart index 7ede275..93a3a03 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -37,14 +37,14 @@ class _AppState extends State with WidgetsBindingObserver { globalIsAppInBackground = false; WidgetsBinding.instance.addObserver(this); - globalCallbackConnectionState = ({required bool isConnected}) async { + globalCallbackConnectionState = ({required isConnected}) async { await context .read() .updateConnectionState(isConnected); await setUserPlan(); }; - globalCallbackUpdatePlan = (SubscriptionPlan plan) { + globalCallbackUpdatePlan = (plan) { context.read().updatePlan(plan); }; @@ -86,8 +86,8 @@ class _AppState extends State with WidgetsBindingObserver { @override void dispose() { WidgetsBinding.instance.removeObserver(this); - globalCallbackConnectionState = ({required bool isConnected}) {}; - globalCallbackUpdatePlan = (SubscriptionPlan planId) {}; + globalCallbackConnectionState = ({required isConnected}) {}; + globalCallbackUpdatePlan = (planId) {}; super.dispose(); } @@ -95,7 +95,7 @@ class _AppState extends State with WidgetsBindingObserver { Widget build(BuildContext context) { return ListenableBuilder( listenable: context.watch(), - builder: (BuildContext context, Widget? child) { + builder: (context, child) { return MaterialApp.router( routerConfig: routerProvider, scaffoldMessengerKey: globalRootScaffoldMessengerKey, diff --git a/lib/globals.dart b/lib/globals.dart index 6196b6f..8731ff1 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -22,12 +22,11 @@ late UserData gUser; // This callback called by the apiProvider void Function({required bool isConnected}) globalCallbackConnectionState = ({ - required bool isConnected, + required isConnected, }) {}; void Function() globalCallbackAppIsOutdated = () {}; void Function() globalCallbackNewDeviceRegistered = () {}; -void Function(SubscriptionPlan plan) globalCallbackUpdatePlan = - (SubscriptionPlan plan) {}; +void Function(SubscriptionPlan plan) globalCallbackUpdatePlan = (plan) {}; Map globalUserDataChangedCallBack = {}; diff --git a/lib/main.dart b/lib/main.dart index ba1eaa8..51543e8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -13,6 +13,7 @@ import 'package:twonly/src/providers/image_editor.provider.dart'; import 'package:twonly/src/providers/purchases.provider.dart'; import 'package:twonly/src/providers/settings.provider.dart'; import 'package:twonly/src/services/api.service.dart'; +import 'package:twonly/src/services/api/mediafiles/download.service.dart'; import 'package:twonly/src/services/api/mediafiles/media_background.service.dart'; import 'package:twonly/src/services/api/mediafiles/upload.service.dart'; import 'package:twonly/src/services/fcm.service.dart'; @@ -73,6 +74,14 @@ void main() async { return u; }); } + if (gUser.appVersion < 91) { + // BUG: Requested media files for reupload where not reuploaded because the wrong state... + await makeMigrationToVersion91(); + await updateUserdata((u) { + u.appVersion = 91; + return u; + }); + } } await twonlyDB.messagesDao.purgeMessageTable(); diff --git a/lib/src/database/daos/mediafiles.dao.dart b/lib/src/database/daos/mediafiles.dao.dart index 414cc29..7edd600 100644 --- a/lib/src/database/daos/mediafiles.dao.dart +++ b/lib/src/database/daos/mediafiles.dao.dart @@ -100,6 +100,14 @@ class MediaFilesDao extends DatabaseAccessor .get(); } + Future> getAllMediaFilesReuploadRequested() async { + return (select(mediaFiles) + ..where( + (t) => t.downloadState.equals(DownloadState.reuploadRequested.name), + )) + .get(); + } + Future> getAllNonHashedStoredMediaFiles() async { return (select(mediaFiles) ..where( diff --git a/lib/src/database/daos/signal.dao.dart b/lib/src/database/daos/signal.dao.dart index b04bc08..2b8e8d7 100644 --- a/lib/src/database/daos/signal.dao.dart +++ b/lib/src/database/daos/signal.dao.dart @@ -93,17 +93,15 @@ class SignalDao extends DatabaseAccessor with _$SignalDaoMixin { await into(signalContactSignedPreKeys).insert(signedPreKey); } - Future purgeOutDatedPreKeys() async { - // Deletion is a workaround for the issue, that own pre keys where deleted after 40 days, while they could be 30days - // on the server + 25 days on the others device old, resulting in the issue that the receiver could not decrypt the - // messages... + Future purgePreKeysFromContact(int contactId) async { await (delete(signalContactPreKeys) ..where( - (t) => (t.createdAt.isSmallerThanValue( - DateTime(2025, 10, 10), - )), + (t) => (t.contactId.equals(contactId)), )) .go(); + } + + Future purgeOutDatedPreKeys() async { // other pre keys are valid 100 days await (delete(signalContactPreKeys) ..where( diff --git a/lib/src/providers/routing.provider.dart b/lib/src/providers/routing.provider.dart index 44dc744..395a7fb 100644 --- a/lib/src/providers/routing.provider.dart +++ b/lib/src/providers/routing.provider.dart @@ -143,7 +143,7 @@ final routerProvider = GoRouter( GoRoute( path: 'modify_avatar', builder: (context, state) => const ModifyAvatarView(), - ) + ), ], ), GoRoute( @@ -199,7 +199,7 @@ final routerProvider = GoRouter( GoRoute( path: 'block_users', builder: (context, state) => const PrivacyViewBlockUsersView(), - ) + ), ], ), GoRoute( diff --git a/lib/src/services/api.service.dart b/lib/src/services/api.service.dart index b7f39c3..816f575 100644 --- a/lib/src/services/api.service.dart +++ b/lib/src/services/api.service.dart @@ -153,9 +153,8 @@ class ApiService { if (connectivitySubscription != null) { return; } - connectivitySubscription = Connectivity() - .onConnectivityChanged - .listen((List result) async { + connectivitySubscription = + Connectivity().onConnectivityChanged.listen((result) async { if (!result.contains(ConnectivityResult.none)) { await connect(); } diff --git a/lib/src/services/api/client2client/media.c2c.dart b/lib/src/services/api/client2client/media.c2c.dart index b99799e..4cef307 100644 --- a/lib/src/services/api/client2client/media.c2c.dart +++ b/lib/src/services/api/client2client/media.c2c.dart @@ -30,12 +30,15 @@ Future handleMedia( if (message == null || message.senderId != fromUserId || message.mediaId == null) { + Log.warn( + 'Got reupload for a message that either does not exists or sender != fromUserId or not a media file', + ); return; } // in case there was already a downloaded file delete it... final mediaService = await MediaFileService.fromMediaId(message.mediaId!); - if (mediaService != null) { + if (mediaService != null && mediaService.tempPath.existsSync()) { mediaService.tempPath.deleteSync(); } @@ -68,6 +71,14 @@ Future handleMedia( mediaType = MediaType.audio; } + final messageTmp = await twonlyDB.messagesDao + .getMessageById(media.senderMessageId) + .getSingleOrNull(); + if (messageTmp != null) { + Log.warn('This message already exit. Message is dropped.'); + return; + } + int? displayLimitInMilliseconds; if (media.hasDisplayLimitInMilliseconds()) { if (media.displayLimitInMilliseconds.toInt() < 1000) { @@ -192,6 +203,13 @@ Future handleMediaUpdate( reuploadRequestedBy: Value(reuploadRequestedBy), ), ); - unawaited(startBackgroundMediaUpload(MediaFileService(mediaFile))); + final mediaFileUpdated = + await MediaFileService.fromMediaId(mediaFile.mediaId); + if (mediaFileUpdated != null) { + if (mediaFileUpdated.uploadRequestPath.existsSync()) { + mediaFileUpdated.uploadRequestPath.deleteSync(); + } + unawaited(startBackgroundMediaUpload(mediaFileUpdated)); + } } } diff --git a/lib/src/services/api/mediafiles/download.service.dart b/lib/src/services/api/mediafiles/download.service.dart index 724da2c..8192ffe 100644 --- a/lib/src/services/api/mediafiles/download.service.dart +++ b/lib/src/services/api/mediafiles/download.service.dart @@ -5,6 +5,7 @@ import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart'; import 'package:cryptography_plus/cryptography_plus.dart'; import 'package:drift/drift.dart'; +import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:mutex/mutex.dart'; import 'package:path/path.dart'; @@ -217,11 +218,20 @@ Future startDownloadMedia(MediaFile media, bool force) async { } } +int failCounter = 0; + Future downloadFileFast( MediaFile media, String apiUrl, File filePath, ) async { + if (failCounter < 2) { + failCounter += 1; + await requestMediaReupload(media.mediaId); + return; + } + failCounter = 0; + final response = await http.get(Uri.parse(apiUrl)).timeout(const Duration(seconds: 10)); @@ -327,3 +337,11 @@ Future handleEncryptedFile(String mediaId) async { unawaited(apiService.downloadDone(mediaService.mediaFile.downloadToken!)); } + +Future makeMigrationToVersion91() async { + final messages = + await twonlyDB.mediaFilesDao.getAllMediaFilesReuploadRequested(); + for (final message in messages) { + await requestMediaReupload(message.mediaId); + } +} diff --git a/lib/src/services/api/mediafiles/upload.service.dart b/lib/src/services/api/mediafiles/upload.service.dart index 6826f20..b1b6073 100644 --- a/lib/src/services/api/mediafiles/upload.service.dart +++ b/lib/src/services/api/mediafiles/upload.service.dart @@ -160,7 +160,9 @@ Future startBackgroundMediaUpload(MediaFileService mediaService) async { if (mediaService.uploadRequestPath.existsSync()) { await mediaService.setUploadState(UploadState.uploading); // at this point the original file is not used any more, so it can be deleted - mediaService.originalPath.deleteSync(); + if (mediaService.originalPath.existsSync()) { + mediaService.originalPath.deleteSync(); + } } } @@ -238,6 +240,10 @@ Future _createUploadRequest(MediaFileService media) async { type = EncryptedContent_Media_Type.VIDEO; } + if (media.mediaFile.reuploadRequestedBy != null) { + type = EncryptedContent_Media_Type.REUPLOAD; + } + final notEncryptedContent = EncryptedContent( groupId: message.groupId, media: EncryptedContent_Media( diff --git a/lib/src/services/api/server_messages.dart b/lib/src/services/api/server_messages.dart index bd2e54f..2a0546f 100644 --- a/lib/src/services/api/server_messages.dart +++ b/lib/src/services/api/server_messages.dart @@ -90,6 +90,11 @@ Future handleClient2ClientMessage(NewMessage newMessage) async { var retry = false; if (message.hasPlaintextContent()) { if (message.plaintextContent.hasDecryptionErrorMessage()) { + if (message.plaintextContent.decryptionErrorMessage.type == + PlaintextContent_DecryptionErrorMessage_Type.PREKEY_UNKNOWN) { + // in case there was a pre key error remove all downloaded pre keys. New pre keys will be fetched automatically. + await twonlyDB.signalDao.purgePreKeysFromContact(fromUserId); + } Log.info( 'Got decryption error: ${message.plaintextContent.decryptionErrorMessage.type} for $receiptId', ); diff --git a/lib/src/views/camera/share_image_contact_selection.view.dart b/lib/src/views/camera/share_image_contact_selection.view.dart index b93fadb..e912200 100644 --- a/lib/src/views/camera/share_image_contact_selection.view.dart +++ b/lib/src/views/camera/share_image_contact_selection.view.dart @@ -1,5 +1,3 @@ -// ignore_for_file: strict_raw_type - import 'dart:async'; import 'dart:collection'; import 'package:flutter/material.dart'; @@ -208,7 +206,7 @@ class _ShareImageView extends State { child: Checkbox( value: !hideArchivedUsers, side: WidgetStateBorderSide.resolveWith( - (Set states) { + (states) { if (states.contains(WidgetState.selected)) { return const BorderSide(width: 0); } @@ -344,7 +342,7 @@ class UserList extends StatelessWidget { return ListView.builder( restorationId: 'new_message_users_list', itemCount: groups.length, - itemBuilder: (BuildContext context, int i) { + itemBuilder: (context, i) { final group = groups[i]; return ListTile( key: ValueKey(group.groupId), @@ -364,14 +362,14 @@ class UserList extends StatelessWidget { trailing: Checkbox( value: selectedGroupIds.contains(group.groupId), side: WidgetStateBorderSide.resolveWith( - (Set states) { + (states) { if (states.contains(WidgetState.selected)) { return const BorderSide(width: 0); } return BorderSide(color: Theme.of(context).colorScheme.outline); }, ), - onChanged: (bool? value) { + onChanged: (value) { if (value == null) return; updateSelectedGroupIds(group.groupId, value); }, diff --git a/lib/src/views/camera/share_image_contact_selection/best_friends_selector.dart b/lib/src/views/camera/share_image_contact_selection/best_friends_selector.dart index 2f96d48..a989497 100644 --- a/lib/src/views/camera/share_image_contact_selection/best_friends_selector.dart +++ b/lib/src/views/camera/share_image_contact_selection/best_friends_selector.dart @@ -163,8 +163,7 @@ class UserCheckbox extends StatelessWidget { Checkbox( value: isChecked, side: WidgetStateBorderSide.resolveWith( - // ignore: strict_raw_type - (Set states) { + (states) { if (states.contains(WidgetState.selected)) { return const BorderSide(width: 0); } @@ -173,7 +172,7 @@ class UserCheckbox extends StatelessWidget { ); }, ), - onChanged: (bool? value) { + onChanged: (value) { onChanged(group.groupId, value ?? false); }, ), diff --git a/lib/src/views/camera/share_image_contact_selection/select_show_time.dart b/lib/src/views/camera/share_image_contact_selection/select_show_time.dart index ed73470..a60618f 100644 --- a/lib/src/views/camera/share_image_contact_selection/select_show_time.dart +++ b/lib/src/views/camera/share_image_contact_selection/select_show_time.dart @@ -51,7 +51,7 @@ class _SelectShowTimeState extends State { scrollController: FixedExtentScrollController( initialItem: widget.initialItem, ), - onSelectedItemChanged: (int selectedItem) { + onSelectedItemChanged: (selectedItem) { _selectedItem = selectedItem; widget.setMaxShowTime( widget.options[selectedItem], diff --git a/lib/src/views/camera/share_image_editor.view.dart b/lib/src/views/camera/share_image_editor.view.dart index d767466..9d12615 100644 --- a/lib/src/views/camera/share_image_editor.view.dart +++ b/lib/src/views/camera/share_image_editor.view.dart @@ -193,7 +193,7 @@ class _ShareImageEditorView extends State { await showModalBottomSheet( context: context, backgroundColor: Colors.black, - builder: (BuildContext context) { + builder: (context) { return SelectShowTime( initialItem: initialItem, setMaxShowTime: _setMaxShowTime, @@ -249,7 +249,7 @@ class _ShareImageEditorView extends State { final layer = await showModalBottomSheet( context: context, backgroundColor: Colors.black, - builder: (BuildContext context) { + builder: (context) { return const EmojiPickerBottom(); }, ) as Layer?; @@ -315,7 +315,7 @@ class _ShareImageEditorView extends State { Future _showBackDialog() { return showDialog( context: context, - builder: (BuildContext context) { + builder: (context) { return AlertDialog( title: Text( context.lang.dialogAskDeleteMediaFilePopTitle, @@ -559,7 +559,6 @@ class _ShareImageEditorView extends State { if (imageLayer.imageLoaded) { timer.cancel(); Future.delayed(const Duration(milliseconds: 50), () { - Log.info(imageLayer.imageLoaded); if (context.mounted) { setState(() { sendingOrLoadingImage = false; @@ -615,7 +614,7 @@ class _ShareImageEditorView extends State { return PopScope( canPop: false, - onPopInvokedWithResult: (bool didPop, bool? result) async { + onPopInvokedWithResult: (didPop, result) async { if (didPop) return; await askToCloseThenClose(); }, diff --git a/lib/src/views/chats/chat_messages_components/chat_reaction_row.dart b/lib/src/views/chats/chat_messages_components/chat_reaction_row.dart index 1a46d10..32c25b9 100644 --- a/lib/src/views/chats/chat_messages_components/chat_reaction_row.dart +++ b/lib/src/views/chats/chat_messages_components/chat_reaction_row.dart @@ -23,7 +23,7 @@ class ReactionRow extends StatelessWidget { await showModalBottomSheet( context: context, backgroundColor: Colors.black, - builder: (BuildContext context) { + builder: (context) { return AllReactionsView( message: message, ); diff --git a/lib/src/views/chats/chat_messages_components/message_context_menu.dart b/lib/src/views/chats/chat_messages_components/message_context_menu.dart index eb868e4..078e657 100644 --- a/lib/src/views/chats/chat_messages_components/message_context_menu.dart +++ b/lib/src/views/chats/chat_messages_components/message_context_menu.dart @@ -86,7 +86,7 @@ class MessageContextMenu extends StatelessWidget { final layer = await showModalBottomSheet( context: context, backgroundColor: Colors.black, - builder: (BuildContext context) { + builder: (context) { return const EmojiPickerBottom(); }, ) as EmojiLayerData?; @@ -210,10 +210,10 @@ Future editTextMessage(BuildContext context, Message message) async { final controller = TextEditingController(text: message.content); await showDialog( context: context, - builder: (BuildContext context) { + builder: (context) { return AlertDialog( content: StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { + builder: (context, setState) { return SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, diff --git a/lib/src/views/chats/media_viewer_components/reaction_buttons.component.dart b/lib/src/views/chats/media_viewer_components/reaction_buttons.component.dart index 108beac..d63dfd8 100644 --- a/lib/src/views/chats/media_viewer_components/reaction_buttons.component.dart +++ b/lib/src/views/chats/media_viewer_components/reaction_buttons.component.dart @@ -119,7 +119,7 @@ class _ReactionButtonsState extends State { final layer = await showModalBottomSheet( context: context, backgroundColor: context.color.surface, - builder: (BuildContext context) { + builder: (context) { return const EmojiPickerBottom(); }, ) as EmojiLayerData?; diff --git a/lib/src/views/chats/message_info.view.dart b/lib/src/views/chats/message_info.view.dart index bfcdb03..7670c48 100644 --- a/lib/src/views/chats/message_info.view.dart +++ b/lib/src/views/chats/message_info.view.dart @@ -211,7 +211,7 @@ class _MessageInfoViewState extends State { await showModalBottomSheet( context: context, backgroundColor: Colors.black, - builder: (BuildContext context) { + builder: (context) { return MessageHistoryView( message: widget.message, changes: messageHistory, diff --git a/lib/src/views/chats/start_new_chat.view.dart b/lib/src/views/chats/start_new_chat.view.dart index fad23f3..09926fc 100644 --- a/lib/src/views/chats/start_new_chat.view.dart +++ b/lib/src/views/chats/start_new_chat.view.dart @@ -154,7 +154,7 @@ class _StartNewChatView extends State { restorationId: 'new_message_users_list', itemCount: filteredContacts.length + 3 + filteredGroups.length, - itemBuilder: (BuildContext context, int i) { + itemBuilder: (context, i) { if (searchUserName.text.isEmpty) { if (i == 0) { return ListTile( diff --git a/lib/src/views/components/alert_dialog.dart b/lib/src/views/components/alert_dialog.dart index 8393e31..b927877 100644 --- a/lib/src/views/components/alert_dialog.dart +++ b/lib/src/views/components/alert_dialog.dart @@ -42,7 +42,7 @@ Future showAlertDialog( // ignore: inference_failure_on_function_invocation await showDialog( context: context, - builder: (BuildContext context) { + builder: (context) { return alert; }, ); diff --git a/lib/src/views/components/context_menu.component.dart b/lib/src/views/components/context_menu.component.dart index 5e6fd62..d41b034 100644 --- a/lib/src/views/components/context_menu.component.dart +++ b/lib/src/views/components/context_menu.component.dart @@ -79,7 +79,7 @@ class _ContextMenuState extends State { Widget build(BuildContext context) { return GestureDetector( onLongPress: _showCustomMenu, - onTapDown: (TapDownDetails details) { + onTapDown: (details) { _tapPosition = details.globalPosition; }, child: widget.child, diff --git a/lib/src/views/components/select_chat_deletion_time.comp.dart b/lib/src/views/components/select_chat_deletion_time.comp.dart index 279a4c7..b756ebb 100644 --- a/lib/src/views/components/select_chat_deletion_time.comp.dart +++ b/lib/src/views/components/select_chat_deletion_time.comp.dart @@ -61,7 +61,7 @@ class _SelectChatDeletionTimeListTitleState Future _showDialog(Widget child) async { await showCupertinoModalPopup( context: context, - builder: (BuildContext context) => Container( + builder: (context) => Container( height: 216, padding: const EdgeInsets.only(top: 6), // The Bottom margin is provided to align the popup above the system navigation bar. @@ -138,13 +138,13 @@ class _SelectChatDeletionTimeListTitleState initialItem: _selectedDeletionTime, ), // This is called when selected item is changed. - onSelectedItemChanged: (int selectedItem) { + onSelectedItemChanged: (selectedItem) { setState(() { _selectedDeletionTime = selectedItem; }); }, children: - List.generate(_getOptions().length, (int index) { + List.generate(_getOptions().length, (index) { return Center( child: Text(_getOptions()[index].$2), ); diff --git a/lib/src/views/contact/contact.view.dart b/lib/src/views/contact/contact.view.dart index 161fa25..1deebbc 100644 --- a/lib/src/views/contact/contact.view.dart +++ b/lib/src/views/contact/contact.view.dart @@ -250,7 +250,7 @@ Future showNicknameChangeDialog( return showDialog( context: context, - builder: (BuildContext context) { + builder: (context) { return AlertDialog( title: Text(context.lang.contactNickname), content: TextField( @@ -287,7 +287,7 @@ Future showReportDialog( return showDialog( context: context, - builder: (BuildContext context) { + builder: (context) { return AlertDialog( title: Text(context.lang.reportUserTitle(getContactDisplayName(contact))), diff --git a/lib/src/views/groups/group.view.dart b/lib/src/views/groups/group.view.dart index 2279dc0..fcfae5f 100644 --- a/lib/src/views/groups/group.view.dart +++ b/lib/src/views/groups/group.view.dart @@ -293,7 +293,7 @@ Future showGroupNameChangeDialog( return showDialog( context: context, - builder: (BuildContext context) { + builder: (context) { return AlertDialog( title: Text(context.lang.groupNameInput), content: TextField( diff --git a/lib/src/views/groups/group_create_select_group_name.view.dart b/lib/src/views/groups/group_create_select_group_name.view.dart index ca7bf9f..4bcc094 100644 --- a/lib/src/views/groups/group_create_select_group_name.view.dart +++ b/lib/src/views/groups/group_create_select_group_name.view.dart @@ -98,7 +98,7 @@ class _GroupCreateSelectGroupNameViewState child: ListView.builder( restorationId: 'new_message_users_list', itemCount: widget.selectedUsers.length, - itemBuilder: (BuildContext context, int i) { + itemBuilder: (context, i) { final user = widget.selectedUsers[i]; return UserContextMenu( key: ValueKey(user.userId), diff --git a/lib/src/views/groups/group_create_select_members.view.dart b/lib/src/views/groups/group_create_select_members.view.dart index c4dfe71..4d1947e 100644 --- a/lib/src/views/groups/group_create_select_members.view.dart +++ b/lib/src/views/groups/group_create_select_members.view.dart @@ -163,7 +163,7 @@ class _StartNewChatView extends State { restorationId: 'new_message_users_list', itemCount: contacts.length + (selectedUsers.isEmpty ? 0 : 2), - itemBuilder: (BuildContext context, int i) { + itemBuilder: (context, i) { if (selectedUsers.isNotEmpty) { final selected = selectedUsers.toList(); if (i == 0) { @@ -230,7 +230,7 @@ class _StartNewChatView extends State { ); }, ), - onChanged: (bool? value) { + onChanged: (value) { toggleSelectedUser(user.userId); }, ), diff --git a/lib/src/views/home.view.dart b/lib/src/views/home.view.dart index 09d40e9..1e24283 100644 --- a/lib/src/views/home.view.dart +++ b/lib/src/views/home.view.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'package:app_links/app_links.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_sharing_intent/model/sharing_file.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:twonly/globals.dart'; @@ -106,8 +105,7 @@ class HomeViewState extends State { activePageIdx = index; }); }; - selectNotificationStream.stream - .listen((NotificationResponse? response) async { + selectNotificationStream.stream.listen((response) async { globalUpdateOfHomeViewPageIndex(0); }); unawaited(_mainCameraController.selectCamera(0, true)); @@ -239,7 +237,7 @@ class HomeViewState extends State { label: '', ), ], - onTap: (int index) async { + onTap: (index) async { activePageIdx = index; await homeViewPageController.animateToPage( index, diff --git a/lib/src/views/memories/memories_photo_slider.view.dart b/lib/src/views/memories/memories_photo_slider.view.dart index 65c16ce..4ac7d35 100644 --- a/lib/src/views/memories/memories_photo_slider.view.dart +++ b/lib/src/views/memories/memories_photo_slider.view.dart @@ -195,7 +195,7 @@ class _MemoriesPhotoSliderViewState extends State { Positioned( right: 5, child: PopupMenuButton( - onSelected: (String result) async { + onSelected: (result) async { if (result == 'delete') { await deleteFile(); } @@ -203,8 +203,7 @@ class _MemoriesPhotoSliderViewState extends State { await exportFile(); } }, - itemBuilder: (BuildContext context) => - >[ + itemBuilder: (context) => >[ PopupMenuItem( value: 'delete', child: Text(context.lang.galleryDelete), diff --git a/lib/src/views/settings/appearance.view.dart b/lib/src/views/settings/appearance.view.dart index 4ea83cd..fa42ac7 100644 --- a/lib/src/views/settings/appearance.view.dart +++ b/lib/src/views/settings/appearance.view.dart @@ -27,7 +27,7 @@ class _AppearanceViewState extends State { // ignore: inference_failure_on_function_invocation await showDialog( context: context, - builder: (BuildContext context) { + builder: (context) { return AlertDialog( title: Text(context.lang.settingsAppearanceTheme), content: Column( @@ -37,7 +37,7 @@ class _AppearanceViewState extends State { value: ThemeMode.system, groupValue: selectedValue, label: 'System default', - onChanged: (ThemeMode? value) { + onChanged: (value) { selectedValue = value; Navigator.of(context).pop(); }, @@ -46,7 +46,7 @@ class _AppearanceViewState extends State { value: ThemeMode.light, groupValue: selectedValue, label: 'Light', - onChanged: (ThemeMode? value) { + onChanged: (value) { selectedValue = value; Navigator.of(context).pop(); }, @@ -55,7 +55,7 @@ class _AppearanceViewState extends State { value: ThemeMode.dark, groupValue: selectedValue, label: 'Dark', - onChanged: (ThemeMode? value) { + onChanged: (value) { selectedValue = value; Navigator.of(context).pop(); }, diff --git a/lib/src/views/settings/backup/backup.view.dart b/lib/src/views/settings/backup/backup.view.dart index f2fc158..cf5945d 100644 --- a/lib/src/views/settings/backup/backup.view.dart +++ b/lib/src/views/settings/backup/backup.view.dart @@ -197,7 +197,7 @@ class _BackupViewState extends State { label: context.lang.backupData, ), ], - onTap: (int index) async { + onTap: (index) async { activePageIdx = index; await pageController.animateToPage( index, diff --git a/lib/src/views/settings/data_and_storage.view.dart b/lib/src/views/settings/data_and_storage.view.dart index 1088a27..9e2d568 100644 --- a/lib/src/views/settings/data_and_storage.view.dart +++ b/lib/src/views/settings/data_and_storage.view.dart @@ -24,7 +24,7 @@ class _DataAndStorageViewState extends State { // ignore: inference_failure_on_function_invocation await showDialog( context: context, - builder: (BuildContext context) { + builder: (context) { return AutoDownloadOptionsDialog( autoDownloadOptions: gUser.autoDownloadOptions ?? defaultAutoDownloadOptions, @@ -172,7 +172,7 @@ class _AutoDownloadOptionsDialogState extends State { title: const Text('Image'), value: autoDownloadOptions[widget.connectionMode.name]! .contains(DownloadMediaTypes.image.name), - onChanged: (bool? value) async { + onChanged: (value) async { await _updateAutoDownloadSetting(DownloadMediaTypes.image, value); }, ), @@ -180,7 +180,7 @@ class _AutoDownloadOptionsDialogState extends State { title: const Text('Video'), value: autoDownloadOptions[widget.connectionMode.name]! .contains(DownloadMediaTypes.video.name), - onChanged: (bool? value) async { + onChanged: (value) async { await _updateAutoDownloadSetting(DownloadMediaTypes.video, value); }, ), diff --git a/lib/src/views/settings/developer/automated_testing.view.dart b/lib/src/views/settings/developer/automated_testing.view.dart index 02a7b9d..d4beded 100644 --- a/lib/src/views/settings/developer/automated_testing.view.dart +++ b/lib/src/views/settings/developer/automated_testing.view.dart @@ -75,7 +75,7 @@ Future showUserNameDialog( await showDialog( context: context, - builder: (BuildContext context) { + builder: (context) { return AlertDialog( title: const Text('Username'), content: TextField( diff --git a/lib/src/views/settings/help/contact_us.view.dart b/lib/src/views/settings/help/contact_us.view.dart index 4aad469..362f63c 100644 --- a/lib/src/views/settings/help/contact_us.view.dart +++ b/lib/src/views/settings/help/contact_us.view.dart @@ -194,12 +194,12 @@ $debugLogToken hint: Text(context.lang.contactUsSelectOption), underline: const SizedBox.shrink(), value: _selectedReason, - onChanged: (String? newValue) { + onChanged: (newValue) { setState(() { _selectedReason = newValue; }); }, - items: reasons.map>((String reason) { + items: reasons.map>((reason) { return DropdownMenuItem( value: reason, child: Text(reason), @@ -211,7 +211,7 @@ $debugLogToken const SizedBox(height: 5), FeedbackEmojiRow( selectedFeedback: _selectedFeedback, - onFeedbackChanged: (int? newValue) { + onFeedbackChanged: (newValue) { setState(() { _selectedFeedback = newValue; }); @@ -317,7 +317,7 @@ class _IncludeDebugLogState extends State { Checkbox( value: widget.isChecked, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - onChanged: (bool? value) { + onChanged: (value) { if (value != null) { widget.onChanged(value); } diff --git a/lib/src/views/settings/privacy_view_block.view.dart b/lib/src/views/settings/privacy_view_block.view.dart index dda1aa1..f3af120 100644 --- a/lib/src/views/settings/privacy_view_block.view.dart +++ b/lib/src/views/settings/privacy_view_block.view.dart @@ -102,7 +102,7 @@ class UserList extends StatelessWidget { return ListView.builder( restorationId: 'new_message_users_list', itemCount: users.length, - itemBuilder: (BuildContext context, int i) { + itemBuilder: (context, i) { final user = users[i]; return UserContextMenu( key: ValueKey(user.userId), @@ -116,7 +116,7 @@ class UserList extends StatelessWidget { leading: AvatarIcon(contactId: user.userId, fontSize: 15), trailing: Checkbox( value: user.blocked, - onChanged: (bool? value) async { + onChanged: (value) async { await block(context, user.userId, value); }, ), diff --git a/lib/src/views/settings/profile/modify_avatar.view.dart b/lib/src/views/settings/profile/modify_avatar.view.dart index 1ba50c0..314b40c 100644 --- a/lib/src/views/settings/profile/modify_avatar.view.dart +++ b/lib/src/views/settings/profile/modify_avatar.view.dart @@ -79,7 +79,7 @@ class _ModifyAvatarViewState extends State { Future _showBackDialog() { return showDialog( context: context, - builder: (BuildContext context) { + builder: (context) { return AlertDialog( title: Text( context.lang.avatarSaveChanges, @@ -117,7 +117,7 @@ class _ModifyAvatarViewState extends State { Widget build(BuildContext context) { return PopScope( canPop: false, - onPopInvokedWithResult: (bool didPop, bool? result) async { + onPopInvokedWithResult: (didPop, result) async { if (didPop) return; if (_avatarMakerController.getJsonOptionsSync() != gUser.avatarJson) { // there where changes diff --git a/lib/src/views/settings/profile/profile.view.dart b/lib/src/views/settings/profile/profile.view.dart index e7b0b56..5132401 100644 --- a/lib/src/views/settings/profile/profile.view.dart +++ b/lib/src/views/settings/profile/profile.view.dart @@ -193,7 +193,7 @@ Future showDisplayNameChangeDialog( return showDialog( context: context, - builder: (BuildContext context) { + builder: (context) { return AlertDialog( title: Text(title), content: TextField( diff --git a/lib/src/views/settings/subscription/select_additional_users.view.dart b/lib/src/views/settings/subscription/select_additional_users.view.dart index 14967c1..2a6d6f1 100644 --- a/lib/src/views/settings/subscription/select_additional_users.view.dart +++ b/lib/src/views/settings/subscription/select_additional_users.view.dart @@ -132,7 +132,7 @@ class _SelectAdditionalUsers extends State { restorationId: 'new_message_users_list', itemCount: contacts.length + (selectedUsers.isEmpty ? 0 : 2), - itemBuilder: (BuildContext context, int i) { + itemBuilder: (context, i) { if (selectedUsers.isNotEmpty) { final selected = selectedUsers.toList(); if (i == 0) { @@ -198,7 +198,7 @@ class _SelectAdditionalUsers extends State { ); }, ), - onChanged: (bool? value) { + onChanged: (value) { toggleSelectedUser(user.userId); }, ), diff --git a/lib/src/views/settings/subscription_custom/select_payment.view.dart b/lib/src/views/settings/subscription_custom/select_payment.view.dart index 15afb5c..9d9c830 100644 --- a/lib/src/views/settings/subscription_custom/select_payment.view.dart +++ b/lib/src/views/settings/subscription_custom/select_payment.view.dart @@ -129,7 +129,7 @@ class _SelectPaymentViewState extends State { Checkbox( value: paymentMethods == PaymentMethods.twonlyCredit, - onChanged: (bool? value) { + onChanged: (value) { setState(() { paymentMethods = PaymentMethods.twonlyCredit; }); diff --git a/lib/src/views/settings/subscription_custom/voucher.view.dart b/lib/src/views/settings/subscription_custom/voucher.view.dart index f2c48dd..770da2f 100644 --- a/lib/src/views/settings/subscription_custom/voucher.view.dart +++ b/lib/src/views/settings/subscription_custom/voucher.view.dart @@ -153,11 +153,11 @@ Future redeemVoucher(BuildContext context) async { // ignore: inference_failure_on_function_invocation await showDialog( context: context, - builder: (BuildContext context) { + builder: (context) { return AlertDialog( title: Text(context.lang.redeemVoucher), content: StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { + builder: (context, setState) { return SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, @@ -227,11 +227,11 @@ Future showBuyVoucher(BuildContext context) async { // ignore: inference_failure_on_function_invocation await showDialog( context: context, - builder: (BuildContext context) { + builder: (context) { return AlertDialog( title: Text(context.lang.createVoucher), content: StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { + builder: (context, setState) { return SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, diff --git a/lib/src/views/user_study/user_study_questionnaire.view.dart b/lib/src/views/user_study/user_study_questionnaire.view.dart index c23a4da..6ae7b2c 100644 --- a/lib/src/views/user_study/user_study_questionnaire.view.dart +++ b/lib/src/views/user_study/user_study_questionnaire.view.dart @@ -179,7 +179,7 @@ class _UserStudyQuestionnaireViewState title: Text(m), visualDensity: const VisualDensity(vertical: -4), value: (_responses['messengers'] as List).contains(m), - onChanged: (bool? value) { + onChanged: (value) { setState(() { value! ? _responses['messengers'].add(m) @@ -236,7 +236,7 @@ class _UserStudyQuestionnaireViewState labelText: 'Bitte wählen...', ), initialValue: _responses[key] as String?, - items: options.map((String value) { + items: options.map((value) { return DropdownMenuItem( value: value, child: Text(value), diff --git a/pubspec.lock b/pubspec.lock index d9c8ce6..240bf52 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d + sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d" url: "https://pub.dev" source: hosted - version: "91.0.0" + version: "93.0.0" _flutterfire_internals: dependency: transitive description: name: _flutterfire_internals - sha256: e4a1b612fd2955908e26116075b3a4baf10c353418ca645b4deae231c82bf144 + sha256: cd83f7d6bd4e4c0b0b4fef802e8796784032e1cc23d7b0e982cf5d05d9bbe182 url: "https://pub.dev" source: hosted - version: "1.3.65" + version: "1.3.66" adaptive_number: dependency: "direct overridden" description: @@ -28,10 +28,10 @@ packages: dependency: transitive description: name: analyzer - sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08 + sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b url: "https://pub.dev" source: hosted - version: "8.4.1" + version: "10.0.1" app_links: dependency: "direct main" description: @@ -92,10 +92,10 @@ packages: dependency: "direct main" description: name: audio_waveforms - sha256: "3a34bdd15dd63a6d1501218449048b28ebe8e1f795bf00ec310acd7b70648f07" + sha256: "03b3430ecf430a2e90185518a228c02be3d26653c62dd931e50d671213a6dbc8" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.0.2" avatar_maker: dependency: "direct main" description: @@ -108,10 +108,10 @@ packages: dependency: "direct main" description: name: background_downloader - sha256: a3b340e42bc45598918944e378dc6a05877e587fcd0e1b8d2ea26339de87bdf9 + sha256: "2ea5322fe836c0aaf96aefd29ef1936771c71927f687cf18168dcc119666a45f" url: "https://pub.dev" source: hosted - version: "9.4.0" + version: "9.5.2" boolean_selector: dependency: transitive description: @@ -124,10 +124,10 @@ packages: dependency: transitive description: name: build - sha256: dfb67ccc9a78c642193e0c2d94cb9e48c2c818b3178a86097d644acdcde6a8d9 + sha256: "275bf6bb2a00a9852c28d4e0b410da1d833a734d57d39d44f94bfc895a484ec3" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.0.4" build_config: dependency: transitive description: @@ -140,18 +140,18 @@ packages: dependency: transitive description: name: build_daemon - sha256: "409002f1adeea601018715d613115cfaf0e31f512cb80ae4534c79867ae2363d" + sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "4.1.1" build_runner: dependency: "direct dev" description: name: build_runner - sha256: a9461b8e586bf018dd4afd2e13b49b08c6a844a4b226c8d1d10f3a723cdd78c3 + sha256: "39ad4ca8a2876779737c60e4228b4bcd35d4352ef7e14e47514093edc012c734" url: "https://pub.dev" source: hosted - version: "2.10.1" + version: "2.11.1" built_collection: dependency: transitive description: @@ -164,10 +164,10 @@ packages: dependency: transitive description: name: built_value - sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d + sha256: "7931c90b84bc573fef103548e354258ae4c9d28d140e41961df6843c5d60d4d8" url: "https://pub.dev" source: hosted - version: "8.12.0" + version: "8.12.3" cached_network_image: dependency: "direct main" description: @@ -196,10 +196,10 @@ packages: dependency: "direct main" description: name: camera - sha256: eefad89f262a873f38d21e5eec853461737ea074d7c9ede39f3ceb135d201cab + sha256: a005c6b9783d895a3a9808d65d06773d13587e22a186b6fe8ef3801b0d12f8cf url: "https://pub.dev" source: hosted - version: "0.11.3" + version: "0.11.3+1" camera_android_camerax: dependency: "direct overridden" description: @@ -213,10 +213,10 @@ packages: dependency: transitive description: name: camera_avfoundation - sha256: "34bcd5db30e52414f1f0783c5e3f566909fab14141a21b3b576c78bd35382bf6" + sha256: "11b4aee2f5e5e038982e152b4a342c749b414aa27857899d20f4323e94cb5f0b" url: "https://pub.dev" source: hosted - version: "0.9.22+4" + version: "0.9.23+2" camera_platform_interface: dependency: transitive description: @@ -229,18 +229,18 @@ packages: dependency: transitive description: name: camera_web - sha256: "595f28c89d1fb62d77c73c633193755b781c6d2e0ebcd8dc25b763b514e6ba8f" + sha256: "57f49a635c8bf249d07fb95eb693d7e4dda6796dedb3777f9127fb54847beba7" url: "https://pub.dev" source: hosted - version: "0.3.5" + version: "0.3.5+3" characters: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" charcode: dependency: transitive description: @@ -273,14 +273,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" + url: "https://pub.dev" + source: hosted + version: "1.0.0" code_builder: dependency: transitive description: name: code_builder - sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243" + sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" url: "https://pub.dev" source: hosted - version: "4.11.0" + version: "4.11.1" collection: dependency: "direct main" description: @@ -317,10 +325,10 @@ packages: dependency: transitive description: name: cross_file - sha256: "942a4791cd385a68ccb3b32c71c427aba508a1bb949b86dff2adbe4049f16239" + sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" url: "https://pub.dev" source: hosted - version: "0.3.5" + version: "0.3.5+2" crypto: dependency: "direct main" description: @@ -357,26 +365,26 @@ packages: dependency: transitive description: name: dart_style - sha256: c87dfe3d56f183ffe9106a18aebc6db431fc7c98c31a54b952a77f3d54a85697 + sha256: "15a7db352c8fc6a4d2bc475ba901c25b39fe7157541da4c16eacce6f8be83e49" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.5" dbus: dependency: transitive description: name: dbus - sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" + sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270 url: "https://pub.dev" source: hosted - version: "0.7.11" + version: "0.7.12" device_info_plus: dependency: "direct main" description: name: device_info_plus - sha256: dd0e8e02186b2196c7848c9d394a5fd6e5b57a43a546082c5820b1ec72317e33 + sha256: "4df8babf73058181227e18b08e6ea3520cf5fc5d796888d33b7cb0f33f984b7c" url: "https://pub.dev" source: hosted - version: "12.2.0" + version: "12.3.0" device_info_plus_platform_interface: dependency: transitive description: @@ -396,26 +404,26 @@ packages: dependency: "direct main" description: name: drift - sha256: "83290a32ae006a7535c5ecf300722cb77177250d9df4ee2becc5fa8a36095114" + sha256: "970cd188fddb111b26ea6a9b07a62bf5c2432d74147b8122c67044ae3b97e99e" url: "https://pub.dev" source: hosted - version: "2.29.0" + version: "2.31.0" drift_dev: dependency: "direct dev" description: name: drift_dev - sha256: "6019f827544e77524ffd5134ae0cb75dfd92ef5ef3e269872af92840c929cd43" + sha256: "917184b2fb867b70a548a83bf0d36268423b38d39968c06cce4905683da49587" url: "https://pub.dev" source: hosted - version: "2.29.0" + version: "2.31.0" drift_flutter: dependency: "direct main" description: name: drift_flutter - sha256: b7534bf320aac5213259aac120670ba67b63a1fd010505babc436ff86083818f + sha256: c07120854742a0cae2f7501a0da02493addde550db6641d284983c08762e60a7 url: "https://pub.dev" source: hosted - version: "0.2.7" + version: "0.2.8" ed25519_edwards: dependency: "direct overridden" description: @@ -444,10 +452,10 @@ packages: dependency: transitive description: name: ffi - sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" ffmpeg_kit_flutter_new: dependency: "direct main" description: @@ -476,50 +484,50 @@ packages: dependency: "direct main" description: name: file_picker - sha256: f8f4ea435f791ab1f817b4e338ed958cb3d04ba43d6736ffc39958d950754967 + sha256: "57d9a1dd5063f85fa3107fb42d1faffda52fdc948cefd5fe5ea85267a5fc7343" url: "https://pub.dev" source: hosted - version: "10.3.6" + version: "10.3.10" file_selector_linux: dependency: transitive description: name: file_selector_linux - sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" + sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0" url: "https://pub.dev" source: hosted - version: "0.9.3+2" + version: "0.9.4" file_selector_macos: dependency: transitive description: name: file_selector_macos - sha256: "88707a3bec4b988aaed3b4df5d7441ee4e987f20b286cddca5d6a8270cab23f2" + sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" url: "https://pub.dev" source: hosted - version: "0.9.4+5" + version: "0.9.5" file_selector_platform_interface: dependency: transitive description: name: file_selector_platform_interface - sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" url: "https://pub.dev" source: hosted - version: "2.6.2" + version: "2.7.0" file_selector_windows: dependency: transitive description: name: file_selector_windows - sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" + sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd" url: "https://pub.dev" source: hosted - version: "0.9.3+4" + version: "0.9.3+5" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: "29cfa93c771d8105484acac340b5ea0835be371672c91405a300303986f4eba9" + sha256: "923085c881663ef685269b013e241b428e1fb03cdd0ebde265d9b40ff18abf80" url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.4.0" firebase_core_platform_interface: dependency: transitive description: @@ -532,34 +540,34 @@ packages: dependency: transitive description: name: firebase_core_web - sha256: a631bbfbfa26963d68046aed949df80b228964020e9155b086eff94f462bbf1f + sha256: "83e7356c704131ca4d8d8dd57e360d8acecbca38b1a3705c7ae46cc34c708084" url: "https://pub.dev" source: hosted - version: "3.3.1" + version: "3.4.0" firebase_messaging: dependency: "direct main" description: name: firebase_messaging - sha256: "1ad663fbb6758acec09d7e84a2e6478265f0a517f40ef77c573efd5e0089f400" + sha256: "06fad40ea14771e969a8f2bbce1944aa20ee2f4f57f4eca5b3ba346b65f3f644" url: "https://pub.dev" source: hosted - version: "16.1.0" + version: "16.1.1" firebase_messaging_platform_interface: dependency: transitive description: name: firebase_messaging_platform_interface - sha256: ea620e841fbcec62a96984295fc628f53ef5a8da4f53238159719ed0af7db834 + sha256: "6c49e901c77e6e10e86d98e32056a087eb1ca1b93acdf58524f1961e617657b7" url: "https://pub.dev" source: hosted - version: "4.7.5" + version: "4.7.6" firebase_messaging_web: dependency: transitive description: name: firebase_messaging_web - sha256: "7d0fb6256202515bba8489a3d69c6bc9d52d69a4999bad789053b486c8e7323e" + sha256: "2756f8fea583ffb9d294d15ddecb3a9ad429b023b70c9990c151fc92c54a32b3" url: "https://pub.dev" source: hosted - version: "4.1.1" + version: "4.1.2" fixnum: dependency: "direct main" description: @@ -735,10 +743,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "306f0596590e077338312f38837f595c04f28d6cdeeac392d3d74df2f0003687" + sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1 url: "https://pub.dev" source: hosted - version: "2.0.32" + version: "2.0.33" flutter_secure_storage: dependency: "direct main" description: @@ -798,10 +806,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: "055de8921be7b8e8b98a233c7a5ef84b3a6fcc32f46f1ebf5b9bb3576d108355" + sha256: "87fbd7c534435b6c5d9d98b01e1fd527812b82e68ddd8bd35fc45ed0fa8f0a95" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" flutter_test: dependency: "direct dev" description: flutter @@ -840,10 +848,10 @@ packages: dependency: "direct main" description: name: get - sha256: c79eeb4339f1f3deffd9ec912f8a923834bec55f7b49c9e882b8fef2c139d425 + sha256: "5ed34a7925b85336e15d472cc4cfe7d9ebf4ab8e8b9f688585bf6b50f4c3d79a" url: "https://pub.dev" source: hosted - version: "4.7.2" + version: "4.7.3" glob: dependency: transitive description: @@ -864,26 +872,26 @@ packages: dependency: "direct main" description: name: google_mlkit_barcode_scanning - sha256: b38505df2d3fdf7830979d60fee55039c2f442d189b2e06fcb2fe494ba65d0db + sha256: dbf4df99cb490b12e95dcedde3e6741ce711942904f7049da601dfede91c1e0f url: "https://pub.dev" source: hosted - version: "0.14.1" + version: "0.14.2" google_mlkit_commons: dependency: transitive description: name: google_mlkit_commons - sha256: "8f40fbac10685cad4715d11e6a0d86837d9ad7168684dfcad29610282a88e67a" + sha256: "3e69fea4211727732cc385104e675ad1e40b29f12edd492ee52fa108423a6124" url: "https://pub.dev" source: hosted - version: "0.11.0" + version: "0.11.1" google_mlkit_face_detection: dependency: "direct main" description: name: google_mlkit_face_detection - sha256: f336737d5b8a86797fd4368f42a5c26aeaa9c6dcc5243f0a16b5f6f663cfb70a + sha256: "7b6ddcc69dbd6fbfa313fb2d974ad0f0c3a0d1657560f0da6be465baf1889687" url: "https://pub.dev" source: hosted - version: "0.13.1" + version: "0.13.2" graphs: dependency: transitive description: @@ -921,6 +929,14 @@ packages: relative: true source: path version: "3.0.1" + hooks: + dependency: transitive + description: + name: hooks + sha256: "7a08a0d684cb3b8fb604b78455d5d352f502b68079f7b80b831c62220ab0a4f6" + url: "https://pub.dev" + source: hosted + version: "1.0.1" html: dependency: "direct main" description: @@ -933,10 +949,10 @@ packages: dependency: "direct main" description: name: http - sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "1.6.0" http_multi_server: dependency: transitive description: @@ -957,42 +973,42 @@ packages: dependency: "direct main" description: name: image - sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" + sha256: "492bd52f6c4fbb6ee41f781ff27765ce5f627910e1e0cbecfa3d9add5562604c" url: "https://pub.dev" source: hosted - version: "4.5.4" + version: "4.7.2" image_picker: dependency: "direct main" description: name: image_picker - sha256: "736eb56a911cf24d1859315ad09ddec0b66104bc41a7f8c5b96b4e2620cf5041" + sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" image_picker_android: dependency: transitive description: name: image_picker_android - sha256: ca2a3b04d34e76157e9ae680ef16014fb4c2d20484e78417eaed6139330056f6 + sha256: "518a16108529fc18657a3e6dde4a043dc465d16596d20ab2abd49a4cac2e703d" url: "https://pub.dev" source: hosted - version: "0.8.13+7" + version: "0.8.13+13" image_picker_for_web: dependency: transitive description: name: image_picker_for_web - sha256: "40c2a6a0da15556dc0f8e38a3246064a971a9f512386c3339b89f76db87269b6" + sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.1" image_picker_ios: dependency: transitive description: name: image_picker_ios - sha256: e675c22790bcc24e9abd455deead2b7a88de4b79f7327a281812f14de1a56f58 + sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588 url: "https://pub.dev" source: hosted - version: "0.8.13+1" + version: "0.8.13+6" image_picker_linux: dependency: transitive description: @@ -1100,18 +1116,26 @@ packages: dependency: "direct main" description: name: json_annotation - sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + sha256: "805fa86df56383000f640384b282ce0cb8431f1a7a2396de92fb66186d8c57df" url: "https://pub.dev" source: hosted - version: "4.9.0" + version: "4.10.0" + json_schema: + dependency: transitive + description: + name: json_schema + sha256: f37d9c3fdfe8c9aae55fdfd5af815d24ce63c3a0f6a2c1f0982c30f43643fa1a + url: "https://pub.dev" + source: hosted + version: "5.2.2" json_serializable: dependency: "direct dev" description: name: json_serializable - sha256: "33a040668b31b320aafa4822b7b1e177e163fc3c1e835c6750319d4ab23aa6fe" + sha256: "93fba3ad139dab2b1ce59ecc6fdce6da46a42cdb6c4399ecda30f1e7e725760d" url: "https://pub.dev" source: hosted - version: "6.11.1" + version: "6.12.0" leak_tracker: dependency: transitive description: @@ -1147,10 +1171,10 @@ packages: dependency: transitive description: name: lints - sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0 + sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "6.1.0" local_auth: dependency: "direct main" description: @@ -1163,18 +1187,18 @@ packages: dependency: transitive description: name: local_auth_android - sha256: d836715ed95b16b2de3a8c47a88ba5e607976bb1e27c9446d193152ea1429fae + sha256: "162b8e177fd9978c4620da2a8002a5c6bed4d20f0c6daf5137e72e9a8b767d2e" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.0.4" local_auth_darwin: dependency: transitive description: name: local_auth_darwin - sha256: "15d9db4ad4d58a11d7269e55d46ff8d49ed5e856226c8a5a91280f0d7c37b3a6" + sha256: "176480aa855ebedeed195e26ac7d6601a45e6b255dfc7433f353e0c1aeafa9a2" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.0.2" local_auth_platform_interface: dependency: transitive description: @@ -1187,10 +1211,10 @@ packages: dependency: transitive description: name: local_auth_windows - sha256: d95535a73eddf57ce5930d5e78a0fa4f294c31981fdeeee83325b797302be454 + sha256: be12c5b8ba5e64896983123655c5f67d2484ecfcc95e367952ad6e3bff94cb16 url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.0.1" logging: dependency: "direct main" description: @@ -1210,18 +1234,18 @@ packages: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.18" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: "direct main" description: @@ -1245,6 +1269,14 @@ packages: relative: true source: path version: "3.1.0" + native_toolchain_c: + dependency: transitive + description: + name: native_toolchain_c + sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac" + url: "https://pub.dev" + source: hosted + version: "0.17.4" nested: dependency: transitive description: @@ -1267,15 +1299,15 @@ packages: path: "dependencies/no_screenshot" relative: true source: path - version: "0.3.2" + version: "0.4.0" objective_c: dependency: transitive description: name: objective_c - sha256: "64e35e1e2e79da4e83f2ace3bf4e5437cef523f46c7db2eba9a1419c49573790" + sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" url: "https://pub.dev" source: hosted - version: "8.0.0" + version: "9.3.0" octo_image: dependency: transitive description: @@ -1343,18 +1375,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: e122c5ea805bb6773bb12ce667611265980940145be920cd09a4b0ec0285cb16 + sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e url: "https://pub.dev" source: hosted - version: "2.2.20" + version: "2.2.22" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: efaec349ddfc181528345c56f8eda9d6cccd71c177511b132c6a0ddaefaa2738 + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" url: "https://pub.dev" source: hosted - version: "2.4.3" + version: "2.6.0" path_provider_linux: dependency: transitive description: @@ -1527,6 +1559,14 @@ packages: relative: true source: path version: "4.1.0" + quiver: + dependency: transitive + description: + name: quiver + sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 + url: "https://pub.dev" + source: hosted + version: "3.2.2" recase: dependency: transitive description: @@ -1543,6 +1583,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + rfc_6901: + dependency: transitive + description: + name: rfc_6901 + sha256: "6a43b1858dca2febaf93e15639aa6b0c49ccdfd7647775f15a499f872b018154" + url: "https://pub.dev" + source: hosted + version: "0.2.1" rxdart: dependency: transitive description: @@ -1563,18 +1611,18 @@ packages: dependency: transitive description: name: sentry - sha256: "10a0bc25f5f21468e3beeae44e561825aaa02cdc6829438e73b9b64658ff88d9" + sha256: e57e57123968b673c8ca19892eecf93113b4811b8e5cd5d165749ca7f7319fdb url: "https://pub.dev" source: hosted - version: "9.8.0" + version: "9.13.0" sentry_flutter: dependency: "direct main" description: name: sentry_flutter - sha256: aafbf41c63c98a30b17bdbf3313424d5102db62b08735c44bff810f277e786a5 + sha256: c8f5b805c0346cd0c93b09e584675af7ed8c3ef36c3589181ad19b4adce35954 url: "https://pub.dev" source: hosted - version: "9.8.0" + version: "9.13.0" share_plus: dependency: "direct main" description: @@ -1595,26 +1643,26 @@ packages: dependency: transitive description: name: shared_preferences - sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64" url: "https://pub.dev" source: hosted - version: "2.5.3" + version: "2.5.4" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "34266009473bf71d748912da4bf62d439185226c03e01e2d9687bc65bbfcb713" + sha256: cbc40be9be1c5af4dab4d6e0de4d5d3729e6f3d65b89d21e1815d57705644a6f url: "https://pub.dev" source: hosted - version: "2.4.15" + version: "2.4.20" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "1c33a907142607c40a7542768ec9badfd16293bac51da3a4482623d15845f88b" + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" url: "https://pub.dev" source: hosted - version: "2.5.5" + version: "2.5.6" shared_preferences_linux: dependency: transitive description: @@ -1672,26 +1720,26 @@ packages: dependency: transitive description: name: source_gen - sha256: "9098ab86015c4f1d8af6486b547b11100e73b193e1899015033cb3e14ad20243" + sha256: "1d562a3c1f713904ebbed50d2760217fd8a51ca170ac4b05b0db490699dbac17" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.2.0" source_helper: dependency: transitive description: name: source_helper - sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723" + sha256: "4a85e90b50694e652075cbe4575665539d253e6ec10e46e76b45368ab5e3caae" url: "https://pub.dev" source: hosted - version: "1.3.8" + version: "1.3.10" source_span: dependency: transitive description: name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" url: "https://pub.dev" source: hosted - version: "1.10.1" + version: "1.10.2" sqflite: dependency: transitive description: @@ -1744,18 +1792,18 @@ packages: dependency: transitive description: name: sqlite3_flutter_libs - sha256: "69c80d812ef2500202ebd22002cbfc1b6565e9ff56b2f971e757fac5d42294df" + sha256: "1e800ebe7f85a80a66adacaa6febe4d5f4d8b75f244e9838a27cb2ffc7aec08d" url: "https://pub.dev" source: hosted - version: "0.5.40" + version: "0.5.41" sqlparser: dependency: transitive description: name: sqlparser - sha256: "54eea43e36dd3769274c3108625f9ea1a382f8d2ac8b16f3e4589d9bd9b0e16c" + sha256: "337e9997f7141ffdd054259128553c348635fa318f7ca492f07a4ab76f850d19" url: "https://pub.dev" source: hosted - version: "0.42.0" + version: "0.43.1" stack_trace: dependency: transitive description: @@ -1808,10 +1856,10 @@ packages: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "19a78f63e83d3a61f00826d09bc2f60e191bf3504183c001262be6ac75589fb8" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.8" timezone: dependency: transitive description: @@ -1828,6 +1876,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + uri: + dependency: transitive + description: + name: uri + sha256: "889eea21e953187c6099802b7b4cf5219ba8f3518f604a1033064d45b1b8268a" + url: "https://pub.dev" + source: hosted + version: "1.0.0" url_launcher: dependency: "direct main" description: @@ -1840,34 +1896,34 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "5c8b6c2d89a78f5a1cca70a73d9d5f86c701b36b42f9c9dac7bad592113c28e9" + sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611" url: "https://pub.dev" source: hosted - version: "6.3.24" + version: "6.3.28" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "6b63f1441e4f653ae799166a72b50b1767321ecc263a57aadf825a7a2a5477d9" + sha256: b1aca26728b7cc7a3af971bb6f601554a8ae9df2e0a006de8450ba06a17ad36a url: "https://pub.dev" source: hosted - version: "6.3.5" + version: "6.4.0" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "8262208506252a3ed4ff5c0dc1e973d2c0e0ef337d0a074d35634da5d44397c9" + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" url: "https://pub.dev" source: hosted - version: "3.2.4" + version: "3.2.5" url_launcher_platform_interface: dependency: transitive description: @@ -1880,18 +1936,18 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" uuid: dependency: transitive description: @@ -1920,10 +1976,10 @@ packages: dependency: transitive description: name: vector_graphics_compiler - sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc + sha256: "201e876b5d52753626af64b6359cd13ac6011b80728731428fd34bc840f71c9b" url: "https://pub.dev" source: hosted - version: "1.1.19" + version: "1.1.20" vector_math: dependency: transitive description: @@ -1936,10 +1992,10 @@ packages: dependency: "direct dev" description: name: very_good_analysis - sha256: "96245839dbcc45dfab1af5fa551603b5c7a282028a64746c19c547d21a7f1e3a" + sha256: "27927d1140ce1b140f998b6340f730a626faa5b95110b3e34a238ff254d731d0" url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "10.1.0" video_player: dependency: "direct main" description: @@ -1952,18 +2008,18 @@ packages: dependency: transitive description: name: video_player_android - sha256: cf768d02924b91e333e2bc1ff928528f57d686445874f383bafab12d0bdfc340 + sha256: ee4fd520b0cafa02e4a867a0f882092e727cdaa1a2d24762171e787f8a502b0a url: "https://pub.dev" source: hosted - version: "2.8.17" + version: "2.9.1" video_player_avfoundation: dependency: transitive description: name: video_player_avfoundation - sha256: "19ed1162a7a5520e7d7791e0b7b73ba03161b6a69428b82e4689e435b325432d" + sha256: f93b93a3baa12ca0ff7d00ca8bc60c1ecd96865568a01ff0c18a99853ee201a5 url: "https://pub.dev" source: hosted - version: "2.8.5" + version: "2.9.3" video_player_platform_interface: dependency: transitive description: @@ -1992,10 +2048,10 @@ packages: dependency: transitive description: name: watcher - sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a" + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" url: "https://pub.dev" source: hosted - version: "1.1.4" + version: "1.2.1" web: dependency: transitive description: @@ -2068,5 +2124,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.10.0 <4.0.0" - flutter: ">=3.38.1" + dart: ">=3.10.3 <4.0.0" + flutter: ">=3.38.4" diff --git a/test/widget_test.dart b/test/widget_test.dart index 1d8332f..0e3e2ac 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -13,7 +13,7 @@ import 'package:flutter_test/flutter_test.dart'; void main() { group('MyWidget', () { - testWidgets('should display a string of text', (WidgetTester tester) async { + testWidgets('should display a string of text', (tester) async { // Define a Widget const myWidget = MaterialApp( home: Scaffold( From 15ae2b5669105aff3ee086651670f2a1e5b67033 Mon Sep 17 00:00:00 2001 From: otsmr Date: Thu, 12 Feb 2026 23:28:24 +0100 Subject: [PATCH 6/8] feature: zoom in media viewer --- CHANGELOG.md | 6 ++++++ .../api/mediafiles/download.service.dart | 9 --------- .../services/api/mediafiles/upload.service.dart | 2 +- lib/src/views/chats/media_viewer.view.dart | 16 ++++++++++++---- .../memories/memories_photo_slider.view.dart | 1 - 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3815b2e..78629bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.0.92 + +- Adds option to zoom in received images / videos +- Fixes issue with "reuploaded requested" not working +- Fixes race condition while writing to the log file + ## 0.0.91 - Fixes link preview on iOS diff --git a/lib/src/services/api/mediafiles/download.service.dart b/lib/src/services/api/mediafiles/download.service.dart index 8192ffe..75368ab 100644 --- a/lib/src/services/api/mediafiles/download.service.dart +++ b/lib/src/services/api/mediafiles/download.service.dart @@ -218,20 +218,11 @@ Future startDownloadMedia(MediaFile media, bool force) async { } } -int failCounter = 0; - Future downloadFileFast( MediaFile media, String apiUrl, File filePath, ) async { - if (failCounter < 2) { - failCounter += 1; - await requestMediaReupload(media.mediaId); - return; - } - failCounter = 0; - final response = await http.get(Uri.parse(apiUrl)).timeout(const Duration(seconds: 10)); diff --git a/lib/src/services/api/mediafiles/upload.service.dart b/lib/src/services/api/mediafiles/upload.service.dart index b1b6073..7dd4f20 100644 --- a/lib/src/services/api/mediafiles/upload.service.dart +++ b/lib/src/services/api/mediafiles/upload.service.dart @@ -378,7 +378,7 @@ Future uploadFileFastOrEnqueue( try { Log.info('Uploading fast: ${task.taskId}'); final response = - await requestMultipart.send().timeout(const Duration(seconds: 4)); + await requestMultipart.send().timeout(const Duration(seconds: 8)); var status = TaskStatus.failed; if (response.statusCode == 200) { status = TaskStatus.complete; diff --git a/lib/src/views/chats/media_viewer.view.dart b/lib/src/views/chats/media_viewer.view.dart index c20edfd..43642af 100644 --- a/lib/src/views/chats/media_viewer.view.dart +++ b/lib/src/views/chats/media_viewer.view.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:lottie/lottie.dart'; import 'package:no_screenshot/no_screenshot.dart'; +import 'package:photo_view/photo_view.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart' @@ -536,15 +537,22 @@ class _MediaViewerViewState extends State { children: [ if (videoController != null) Positioned.fill( - child: VideoPlayer(videoController!), + child: PhotoView.customChild( + initialScale: PhotoViewComputedScale.contained, + minScale: PhotoViewComputedScale.contained, + child: VideoPlayer(videoController!), + ), ) else if (currentMedia != null && currentMedia!.mediaFile.type == MediaType.image || currentMedia!.mediaFile.type == MediaType.gif) Positioned.fill( - child: Image.file( - currentMedia!.tempPath, - fit: BoxFit.contain, + child: PhotoView( + imageProvider: FileImage( + currentMedia!.tempPath, + ), + initialScale: PhotoViewComputedScale.contained, + minScale: PhotoViewComputedScale.contained, ), ), ], diff --git a/lib/src/views/memories/memories_photo_slider.view.dart b/lib/src/views/memories/memories_photo_slider.view.dart index 4ac7d35..a44dbec 100644 --- a/lib/src/views/memories/memories_photo_slider.view.dart +++ b/lib/src/views/memories/memories_photo_slider.view.dart @@ -242,7 +242,6 @@ class _MemoriesPhotoSliderViewState extends State { child: VideoPlayerWrapper( videoPath: filePath, ), - // childSize: const Size(300, 300), initialScale: PhotoViewComputedScale.contained, minScale: PhotoViewComputedScale.contained, maxScale: PhotoViewComputedScale.covered * 4.1, From 835ee9ee2d3a0e76f9a2b697ddd39fe3988ce0e8 Mon Sep 17 00:00:00 2001 From: otsmr Date: Fri, 13 Feb 2026 02:16:04 +0100 Subject: [PATCH 7/8] feature: share contacts --- lib/src/database/daos/contacts.dao.g.dart | 8 + lib/src/database/daos/groups.dao.g.dart | 15 + lib/src/database/daos/mediafiles.dao.g.dart | 8 + lib/src/database/daos/messages.dao.dart | 10 +- lib/src/database/daos/messages.dao.g.dart | 24 ++ lib/src/database/daos/reactions.dao.g.dart | 16 ++ lib/src/database/daos/receipts.dao.g.dart | 22 ++ lib/src/database/daos/signal.dao.g.dart | 15 + lib/src/database/tables/messages.table.dart | 4 +- lib/src/database/twonly.db.g.dart | 60 ++-- .../generated/app_localizations.dart | 24 ++ .../generated/app_localizations_de.dart | 13 + .../generated/app_localizations_en.dart | 13 + .../generated/app_localizations_sv.dart | 13 + lib/src/localization/translations | 2 +- lib/src/model/protobuf/client/data.proto | 7 + .../protobuf/client/generated/data.pb.dart | 89 ++++++ .../client/generated/data.pbenum.dart | 5 +- .../client/generated/data.pbjson.dart | 36 ++- .../client/generated/messages.pb.dart | 120 ++++++++ .../client/generated/messages.pbjson.dart | 160 +++++++---- lib/src/model/protobuf/client/messages.proto | 9 +- .../client2client/additional_data.c2c.dart | 36 +++ .../services/api/client2client/media.c2c.dart | 2 +- .../api/client2client/text_message.c2c.dart | 2 +- .../api/mediafiles/download.service.dart | 1 - .../api/mediafiles/upload.service.dart | 2 +- lib/src/services/api/messages.dart | 60 +++- lib/src/services/api/server_messages.dart | 10 + lib/src/services/api/utils.dart | 22 ++ .../notifications/pushkeys.notifications.dart | 5 + lib/src/utils/misc.dart | 12 +- lib/src/utils/qr.dart | 23 +- lib/src/views/chats/add_new_user.view.dart | 23 +- .../chat_list_components/group_list_item.dart | 12 +- lib/src/views/chats/chat_messages.view.dart | 4 +- .../share_additional.bottom_sheet.dart | 112 ++++++++ .../chat_list_entry.dart | 74 +++-- .../entries/chat_contacts.entry.dart | 201 +++++++++++++ .../entries/chat_media_entry.dart | 2 +- .../entries/chat_unkown.entry.dart | 29 ++ .../entries/common.dart | 4 +- .../message_context_menu.dart | 2 +- .../message_input.dart | 41 ++- .../message_send_state_icon.dart | 6 +- .../response_container.dart | 4 +- .../additional_message_content.dart | 1 + lib/src/views/contact/contact.view.dart | 12 +- .../views/shared/select_contacts.view.dart | 271 ++++++++++++++++++ 49 files changed, 1432 insertions(+), 214 deletions(-) create mode 100644 lib/src/services/api/client2client/additional_data.c2c.dart create mode 100644 lib/src/views/chats/chat_messages_components/bottom_sheets/share_additional.bottom_sheet.dart create mode 100644 lib/src/views/chats/chat_messages_components/entries/chat_contacts.entry.dart create mode 100644 lib/src/views/chats/chat_messages_components/entries/chat_unkown.entry.dart create mode 100644 lib/src/views/shared/select_contacts.view.dart diff --git a/lib/src/database/daos/contacts.dao.g.dart b/lib/src/database/daos/contacts.dao.g.dart index 626cccb..f76b607 100644 --- a/lib/src/database/daos/contacts.dao.g.dart +++ b/lib/src/database/daos/contacts.dao.g.dart @@ -5,4 +5,12 @@ part of 'contacts.dao.dart'; // ignore_for_file: type=lint mixin _$ContactsDaoMixin on DatabaseAccessor { $ContactsTable get contacts => attachedDatabase.contacts; + ContactsDaoManager get managers => ContactsDaoManager(this); +} + +class ContactsDaoManager { + final _$ContactsDaoMixin _db; + ContactsDaoManager(this._db); + $$ContactsTableTableManager get contacts => + $$ContactsTableTableManager(_db.attachedDatabase, _db.contacts); } diff --git a/lib/src/database/daos/groups.dao.g.dart b/lib/src/database/daos/groups.dao.g.dart index 4f873ec..2b5ae1b 100644 --- a/lib/src/database/daos/groups.dao.g.dart +++ b/lib/src/database/daos/groups.dao.g.dart @@ -8,4 +8,19 @@ mixin _$GroupsDaoMixin on DatabaseAccessor { $ContactsTable get contacts => attachedDatabase.contacts; $GroupMembersTable get groupMembers => attachedDatabase.groupMembers; $GroupHistoriesTable get groupHistories => attachedDatabase.groupHistories; + GroupsDaoManager get managers => GroupsDaoManager(this); +} + +class GroupsDaoManager { + final _$GroupsDaoMixin _db; + GroupsDaoManager(this._db); + $$GroupsTableTableManager get groups => + $$GroupsTableTableManager(_db.attachedDatabase, _db.groups); + $$ContactsTableTableManager get contacts => + $$ContactsTableTableManager(_db.attachedDatabase, _db.contacts); + $$GroupMembersTableTableManager get groupMembers => + $$GroupMembersTableTableManager(_db.attachedDatabase, _db.groupMembers); + $$GroupHistoriesTableTableManager get groupHistories => + $$GroupHistoriesTableTableManager( + _db.attachedDatabase, _db.groupHistories); } diff --git a/lib/src/database/daos/mediafiles.dao.g.dart b/lib/src/database/daos/mediafiles.dao.g.dart index 0157e2d..3bc0ce8 100644 --- a/lib/src/database/daos/mediafiles.dao.g.dart +++ b/lib/src/database/daos/mediafiles.dao.g.dart @@ -5,4 +5,12 @@ part of 'mediafiles.dao.dart'; // ignore_for_file: type=lint mixin _$MediaFilesDaoMixin on DatabaseAccessor { $MediaFilesTable get mediaFiles => attachedDatabase.mediaFiles; + MediaFilesDaoManager get managers => MediaFilesDaoManager(this); +} + +class MediaFilesDaoManager { + final _$MediaFilesDaoMixin _db; + MediaFilesDaoManager(this._db); + $$MediaFilesTableTableManager get mediaFiles => + $$MediaFilesTableTableManager(_db.attachedDatabase, _db.mediaFiles); } diff --git a/lib/src/database/daos/messages.dao.dart b/lib/src/database/daos/messages.dao.dart index 72a8d59..1e41efc 100644 --- a/lib/src/database/daos/messages.dao.dart +++ b/lib/src/database/daos/messages.dao.dart @@ -75,10 +75,12 @@ class MessagesDao extends DatabaseAccessor with _$MessagesDaoMixin { (t) => t.groupId.equals(groupId) & (t.isDeletedFromSender.equals(true) | - ((t.type.equals(MessageType.text.name) & - t.content.isNotNull()) | - (t.type.equals(MessageType.media.name) & - t.mediaId.isNotNull()))), + (t.type.equals(MessageType.text.name).not() | + t.type.equals(MessageType.media.name).not()) | + (t.type.equals(MessageType.text.name) & + t.content.isNotNull()) | + (t.type.equals(MessageType.media.name) & + t.mediaId.isNotNull())), )) ..orderBy([(t) => OrderingTerm.asc(t.createdAt)])) .watch(); diff --git a/lib/src/database/daos/messages.dao.g.dart b/lib/src/database/daos/messages.dao.g.dart index feb9283..3fb2892 100644 --- a/lib/src/database/daos/messages.dao.g.dart +++ b/lib/src/database/daos/messages.dao.g.dart @@ -13,4 +13,28 @@ mixin _$MessagesDaoMixin on DatabaseAccessor { attachedDatabase.messageHistories; $GroupMembersTable get groupMembers => attachedDatabase.groupMembers; $MessageActionsTable get messageActions => attachedDatabase.messageActions; + MessagesDaoManager get managers => MessagesDaoManager(this); +} + +class MessagesDaoManager { + final _$MessagesDaoMixin _db; + MessagesDaoManager(this._db); + $$GroupsTableTableManager get groups => + $$GroupsTableTableManager(_db.attachedDatabase, _db.groups); + $$ContactsTableTableManager get contacts => + $$ContactsTableTableManager(_db.attachedDatabase, _db.contacts); + $$MediaFilesTableTableManager get mediaFiles => + $$MediaFilesTableTableManager(_db.attachedDatabase, _db.mediaFiles); + $$MessagesTableTableManager get messages => + $$MessagesTableTableManager(_db.attachedDatabase, _db.messages); + $$ReactionsTableTableManager get reactions => + $$ReactionsTableTableManager(_db.attachedDatabase, _db.reactions); + $$MessageHistoriesTableTableManager get messageHistories => + $$MessageHistoriesTableTableManager( + _db.attachedDatabase, _db.messageHistories); + $$GroupMembersTableTableManager get groupMembers => + $$GroupMembersTableTableManager(_db.attachedDatabase, _db.groupMembers); + $$MessageActionsTableTableManager get messageActions => + $$MessageActionsTableTableManager( + _db.attachedDatabase, _db.messageActions); } diff --git a/lib/src/database/daos/reactions.dao.g.dart b/lib/src/database/daos/reactions.dao.g.dart index 26ac0da..a87f9a8 100644 --- a/lib/src/database/daos/reactions.dao.g.dart +++ b/lib/src/database/daos/reactions.dao.g.dart @@ -9,4 +9,20 @@ mixin _$ReactionsDaoMixin on DatabaseAccessor { $MediaFilesTable get mediaFiles => attachedDatabase.mediaFiles; $MessagesTable get messages => attachedDatabase.messages; $ReactionsTable get reactions => attachedDatabase.reactions; + ReactionsDaoManager get managers => ReactionsDaoManager(this); +} + +class ReactionsDaoManager { + final _$ReactionsDaoMixin _db; + ReactionsDaoManager(this._db); + $$GroupsTableTableManager get groups => + $$GroupsTableTableManager(_db.attachedDatabase, _db.groups); + $$ContactsTableTableManager get contacts => + $$ContactsTableTableManager(_db.attachedDatabase, _db.contacts); + $$MediaFilesTableTableManager get mediaFiles => + $$MediaFilesTableTableManager(_db.attachedDatabase, _db.mediaFiles); + $$MessagesTableTableManager get messages => + $$MessagesTableTableManager(_db.attachedDatabase, _db.messages); + $$ReactionsTableTableManager get reactions => + $$ReactionsTableTableManager(_db.attachedDatabase, _db.reactions); } diff --git a/lib/src/database/daos/receipts.dao.g.dart b/lib/src/database/daos/receipts.dao.g.dart index 4230aa8..9cada53 100644 --- a/lib/src/database/daos/receipts.dao.g.dart +++ b/lib/src/database/daos/receipts.dao.g.dart @@ -12,4 +12,26 @@ mixin _$ReceiptsDaoMixin on DatabaseAccessor { $MessageActionsTable get messageActions => attachedDatabase.messageActions; $ReceivedReceiptsTable get receivedReceipts => attachedDatabase.receivedReceipts; + ReceiptsDaoManager get managers => ReceiptsDaoManager(this); +} + +class ReceiptsDaoManager { + final _$ReceiptsDaoMixin _db; + ReceiptsDaoManager(this._db); + $$ContactsTableTableManager get contacts => + $$ContactsTableTableManager(_db.attachedDatabase, _db.contacts); + $$GroupsTableTableManager get groups => + $$GroupsTableTableManager(_db.attachedDatabase, _db.groups); + $$MediaFilesTableTableManager get mediaFiles => + $$MediaFilesTableTableManager(_db.attachedDatabase, _db.mediaFiles); + $$MessagesTableTableManager get messages => + $$MessagesTableTableManager(_db.attachedDatabase, _db.messages); + $$ReceiptsTableTableManager get receipts => + $$ReceiptsTableTableManager(_db.attachedDatabase, _db.receipts); + $$MessageActionsTableTableManager get messageActions => + $$MessageActionsTableTableManager( + _db.attachedDatabase, _db.messageActions); + $$ReceivedReceiptsTableTableManager get receivedReceipts => + $$ReceivedReceiptsTableTableManager( + _db.attachedDatabase, _db.receivedReceipts); } diff --git a/lib/src/database/daos/signal.dao.g.dart b/lib/src/database/daos/signal.dao.g.dart index a9eea13..68c90c1 100644 --- a/lib/src/database/daos/signal.dao.g.dart +++ b/lib/src/database/daos/signal.dao.g.dart @@ -9,4 +9,19 @@ mixin _$SignalDaoMixin on DatabaseAccessor { attachedDatabase.signalContactPreKeys; $SignalContactSignedPreKeysTable get signalContactSignedPreKeys => attachedDatabase.signalContactSignedPreKeys; + SignalDaoManager get managers => SignalDaoManager(this); +} + +class SignalDaoManager { + final _$SignalDaoMixin _db; + SignalDaoManager(this._db); + $$ContactsTableTableManager get contacts => + $$ContactsTableTableManager(_db.attachedDatabase, _db.contacts); + $$SignalContactPreKeysTableTableManager get signalContactPreKeys => + $$SignalContactPreKeysTableTableManager( + _db.attachedDatabase, _db.signalContactPreKeys); + $$SignalContactSignedPreKeysTableTableManager + get signalContactSignedPreKeys => + $$SignalContactSignedPreKeysTableTableManager( + _db.attachedDatabase, _db.signalContactSignedPreKeys); } diff --git a/lib/src/database/tables/messages.table.dart b/lib/src/database/tables/messages.table.dart index 89e2975..b5bd471 100644 --- a/lib/src/database/tables/messages.table.dart +++ b/lib/src/database/tables/messages.table.dart @@ -3,7 +3,7 @@ import 'package:twonly/src/database/tables/contacts.table.dart'; import 'package:twonly/src/database/tables/groups.table.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; -enum MessageType { media, text } +enum MessageType { media, text, contacts } @DataClassName('Message') class Messages extends Table { @@ -15,7 +15,7 @@ class Messages extends Table { IntColumn get senderId => integer().nullable().references(Contacts, #userId)(); - TextColumn get type => textEnum()(); + TextColumn get type => text()(); TextColumn get content => text().nullable()(); TextColumn get mediaId => text() diff --git a/lib/src/database/twonly.db.g.dart b/lib/src/database/twonly.db.g.dart index bad66a1..6502008 100644 --- a/lib/src/database/twonly.db.g.dart +++ b/lib/src/database/twonly.db.g.dart @@ -2776,11 +2776,11 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> { requiredDuringInsert: false, defaultConstraints: GeneratedColumn.constraintIsAlways('REFERENCES contacts (user_id)')); + static const VerificationMeta _typeMeta = const VerificationMeta('type'); @override - late final GeneratedColumnWithTypeConverter type = - GeneratedColumn('type', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true) - .withConverter($MessagesTable.$convertertype); + late final GeneratedColumn type = GeneratedColumn( + 'type', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); static const VerificationMeta _contentMeta = const VerificationMeta('content'); @override @@ -2929,6 +2929,12 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> { context.handle(_senderIdMeta, senderId.isAcceptableOrUnknown(data['sender_id']!, _senderIdMeta)); } + if (data.containsKey('type')) { + context.handle( + _typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); + } else if (isInserting) { + context.missing(_typeMeta); + } if (data.containsKey('content')) { context.handle(_contentMeta, content.isAcceptableOrUnknown(data['content']!, _contentMeta)); @@ -3020,8 +3026,8 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> { .read(DriftSqlType.string, data['${effectivePrefix}message_id'])!, senderId: attachedDatabase.typeMapping .read(DriftSqlType.int, data['${effectivePrefix}sender_id']), - type: $MessagesTable.$convertertype.fromSql(attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}type'])!), + type: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}type'])!, content: attachedDatabase.typeMapping .read(DriftSqlType.string, data['${effectivePrefix}content']), mediaId: attachedDatabase.typeMapping @@ -3057,16 +3063,13 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> { $MessagesTable createAlias(String alias) { return $MessagesTable(attachedDatabase, alias); } - - static JsonTypeConverter2 $convertertype = - const EnumNameConverter(MessageType.values); } class Message extends DataClass implements Insertable { final String groupId; final String messageId; final int? senderId; - final MessageType type; + final String type; final String? content; final String? mediaId; final Uint8List? additionalMessageData; @@ -3108,9 +3111,7 @@ class Message extends DataClass implements Insertable { if (!nullToAbsent || senderId != null) { map['sender_id'] = Variable(senderId); } - { - map['type'] = Variable($MessagesTable.$convertertype.toSql(type)); - } + map['type'] = Variable(type); if (!nullToAbsent || content != null) { map['content'] = Variable(content); } @@ -3201,8 +3202,7 @@ class Message extends DataClass implements Insertable { groupId: serializer.fromJson(json['groupId']), messageId: serializer.fromJson(json['messageId']), senderId: serializer.fromJson(json['senderId']), - type: $MessagesTable.$convertertype - .fromJson(serializer.fromJson(json['type'])), + type: serializer.fromJson(json['type']), content: serializer.fromJson(json['content']), mediaId: serializer.fromJson(json['mediaId']), additionalMessageData: @@ -3228,8 +3228,7 @@ class Message extends DataClass implements Insertable { 'groupId': serializer.toJson(groupId), 'messageId': serializer.toJson(messageId), 'senderId': serializer.toJson(senderId), - 'type': - serializer.toJson($MessagesTable.$convertertype.toJson(type)), + 'type': serializer.toJson(type), 'content': serializer.toJson(content), 'mediaId': serializer.toJson(mediaId), 'additionalMessageData': @@ -3252,7 +3251,7 @@ class Message extends DataClass implements Insertable { {String? groupId, String? messageId, Value senderId = const Value.absent(), - MessageType? type, + String? type, Value content = const Value.absent(), Value mediaId = const Value.absent(), Value additionalMessageData = const Value.absent(), @@ -3403,7 +3402,7 @@ class MessagesCompanion extends UpdateCompanion { final Value groupId; final Value messageId; final Value senderId; - final Value type; + final Value type; final Value content; final Value mediaId; final Value additionalMessageData; @@ -3444,7 +3443,7 @@ class MessagesCompanion extends UpdateCompanion { required String groupId, required String messageId, this.senderId = const Value.absent(), - required MessageType type, + required String type, this.content = const Value.absent(), this.mediaId = const Value.absent(), this.additionalMessageData = const Value.absent(), @@ -3513,7 +3512,7 @@ class MessagesCompanion extends UpdateCompanion { {Value? groupId, Value? messageId, Value? senderId, - Value? type, + Value? type, Value? content, Value? mediaId, Value? additionalMessageData, @@ -3566,8 +3565,7 @@ class MessagesCompanion extends UpdateCompanion { map['sender_id'] = Variable(senderId.value); } if (type.present) { - map['type'] = - Variable($MessagesTable.$convertertype.toSql(type.value)); + map['type'] = Variable(type.value); } if (content.present) { map['content'] = Variable(content.value); @@ -10148,7 +10146,7 @@ typedef $$MessagesTableCreateCompanionBuilder = MessagesCompanion Function({ required String groupId, required String messageId, Value senderId, - required MessageType type, + required String type, Value content, Value mediaId, Value additionalMessageData, @@ -10169,7 +10167,7 @@ typedef $$MessagesTableUpdateCompanionBuilder = MessagesCompanion Function({ Value groupId, Value messageId, Value senderId, - Value type, + Value type, Value content, Value mediaId, Value additionalMessageData, @@ -10314,10 +10312,8 @@ class $$MessagesTableFilterComposer ColumnFilters get messageId => $composableBuilder( column: $table.messageId, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters get type => - $composableBuilder( - column: $table.type, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnFilters get type => $composableBuilder( + column: $table.type, builder: (column) => ColumnFilters(column)); ColumnFilters get content => $composableBuilder( column: $table.content, builder: (column) => ColumnFilters(column)); @@ -10638,7 +10634,7 @@ class $$MessagesTableAnnotationComposer GeneratedColumn get messageId => $composableBuilder(column: $table.messageId, builder: (column) => column); - GeneratedColumnWithTypeConverter get type => + GeneratedColumn get type => $composableBuilder(column: $table.type, builder: (column) => column); GeneratedColumn get content => @@ -10858,7 +10854,7 @@ class $$MessagesTableTableManager extends RootTableManager< Value groupId = const Value.absent(), Value messageId = const Value.absent(), Value senderId = const Value.absent(), - Value type = const Value.absent(), + Value type = const Value.absent(), Value content = const Value.absent(), Value mediaId = const Value.absent(), Value additionalMessageData = const Value.absent(), @@ -10900,7 +10896,7 @@ class $$MessagesTableTableManager extends RootTableManager< required String groupId, required String messageId, Value senderId = const Value.absent(), - required MessageType type, + required String type, Value content = const Value.absent(), Value mediaId = const Value.absent(), Value additionalMessageData = const Value.absent(), diff --git a/lib/src/localization/generated/app_localizations.dart b/lib/src/localization/generated/app_localizations.dart index 7b6c04b..1821ee6 100644 --- a/lib/src/localization/generated/app_localizations.dart +++ b/lib/src/localization/generated/app_localizations.dart @@ -2967,6 +2967,30 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'You must authenticate to reopen the image.'** String get authRequestReopenImage; + + /// No description provided for @shareContactsMenu. + /// + /// In en, this message translates to: + /// **'Contact'** + String get shareContactsMenu; + + /// No description provided for @shareContactsTitle. + /// + /// In en, this message translates to: + /// **'Select contacts'** + String get shareContactsTitle; + + /// No description provided for @shareContactsSubmit. + /// + /// In en, this message translates to: + /// **'Share now'** + String get shareContactsSubmit; + + /// No description provided for @updateTwonlyMessage. + /// + /// In en, this message translates to: + /// **'To see this message, you need to update twonly.'** + String get updateTwonlyMessage; } class _AppLocalizationsDelegate diff --git a/lib/src/localization/generated/app_localizations_de.dart b/lib/src/localization/generated/app_localizations_de.dart index 3a3c99a..4385fee 100644 --- a/lib/src/localization/generated/app_localizations_de.dart +++ b/lib/src/localization/generated/app_localizations_de.dart @@ -1656,4 +1656,17 @@ class AppLocalizationsDe extends AppLocalizations { @override String get authRequestReopenImage => 'Um das Bild erneut zu öffnen, musst du dich authentifizieren.'; + + @override + String get shareContactsMenu => 'Kontakt'; + + @override + String get shareContactsTitle => 'Kontakte auswählen'; + + @override + String get shareContactsSubmit => 'Jetzt teilen'; + + @override + String get updateTwonlyMessage => + 'Um diese Nachricht zu sehen, musst du twonly aktualisieren.'; } diff --git a/lib/src/localization/generated/app_localizations_en.dart b/lib/src/localization/generated/app_localizations_en.dart index e4d51a4..62fee3d 100644 --- a/lib/src/localization/generated/app_localizations_en.dart +++ b/lib/src/localization/generated/app_localizations_en.dart @@ -1644,4 +1644,17 @@ class AppLocalizationsEn extends AppLocalizations { @override String get authRequestReopenImage => 'You must authenticate to reopen the image.'; + + @override + String get shareContactsMenu => 'Contact'; + + @override + String get shareContactsTitle => 'Select contacts'; + + @override + String get shareContactsSubmit => 'Share now'; + + @override + String get updateTwonlyMessage => + 'To see this message, you need to update twonly.'; } diff --git a/lib/src/localization/generated/app_localizations_sv.dart b/lib/src/localization/generated/app_localizations_sv.dart index 05a47b1..286fb3b 100644 --- a/lib/src/localization/generated/app_localizations_sv.dart +++ b/lib/src/localization/generated/app_localizations_sv.dart @@ -1644,4 +1644,17 @@ class AppLocalizationsSv extends AppLocalizations { @override String get authRequestReopenImage => 'You must authenticate to reopen the image.'; + + @override + String get shareContactsMenu => 'Contact'; + + @override + String get shareContactsTitle => 'Select contacts'; + + @override + String get shareContactsSubmit => 'Share now'; + + @override + String get updateTwonlyMessage => + 'To see this message, you need to update twonly.'; } diff --git a/lib/src/localization/translations b/lib/src/localization/translations index 4caaa3d..69d295d 160000 --- a/lib/src/localization/translations +++ b/lib/src/localization/translations @@ -1 +1 @@ -Subproject commit 4caaa3d91aaf1ac2f13160ba770a2880c26bd229 +Subproject commit 69d295db737253e0c1b68aedc39bf757e8d642e6 diff --git a/lib/src/model/protobuf/client/data.proto b/lib/src/model/protobuf/client/data.proto index 20a3b48..f708fe0 100644 --- a/lib/src/model/protobuf/client/data.proto +++ b/lib/src/model/protobuf/client/data.proto @@ -1,11 +1,18 @@ syntax = "proto3"; +message SharedContact { + int64 user_id = 1; + bytes public_identity_key = 2; + string display_name = 3; +} message AdditionalMessageData { enum Type { LINK = 0; + CONTACTS = 1; } Type type = 1; optional string link = 2; + repeated SharedContact contacts = 3; } \ No newline at end of file diff --git a/lib/src/model/protobuf/client/generated/data.pb.dart b/lib/src/model/protobuf/client/generated/data.pb.dart index 9b547b3..109b83e 100644 --- a/lib/src/model/protobuf/client/generated/data.pb.dart +++ b/lib/src/model/protobuf/client/generated/data.pb.dart @@ -12,6 +12,7 @@ import 'dart:core' as $core; +import 'package:fixnum/fixnum.dart' as $fixnum; import 'package:protobuf/protobuf.dart' as $pb; import 'data.pbenum.dart'; @@ -20,14 +21,96 @@ export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions; export 'data.pbenum.dart'; +class SharedContact extends $pb.GeneratedMessage { + factory SharedContact({ + $fixnum.Int64? userId, + $core.List<$core.int>? publicIdentityKey, + $core.String? displayName, + }) { + final result = create(); + if (userId != null) result.userId = userId; + if (publicIdentityKey != null) result.publicIdentityKey = publicIdentityKey; + if (displayName != null) result.displayName = displayName; + return result; + } + + SharedContact._(); + + factory SharedContact.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory SharedContact.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'SharedContact', + createEmptyInstance: create) + ..aInt64(1, _omitFieldNames ? '' : 'userId') + ..a<$core.List<$core.int>>( + 2, _omitFieldNames ? '' : 'publicIdentityKey', $pb.PbFieldType.OY) + ..aOS(3, _omitFieldNames ? '' : 'displayName') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SharedContact clone() => SharedContact()..mergeFromMessage(this); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SharedContact copyWith(void Function(SharedContact) updates) => + super.copyWith((message) => updates(message as SharedContact)) + as SharedContact; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SharedContact create() => SharedContact._(); + @$core.override + SharedContact createEmptyInstance() => create(); + static $pb.PbList createRepeated() => + $pb.PbList(); + @$core.pragma('dart2js:noInline') + static SharedContact getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static SharedContact? _defaultInstance; + + @$pb.TagNumber(1) + $fixnum.Int64 get userId => $_getI64(0); + @$pb.TagNumber(1) + set userId($fixnum.Int64 value) => $_setInt64(0, value); + @$pb.TagNumber(1) + $core.bool hasUserId() => $_has(0); + @$pb.TagNumber(1) + void clearUserId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.List<$core.int> get publicIdentityKey => $_getN(1); + @$pb.TagNumber(2) + set publicIdentityKey($core.List<$core.int> value) => $_setBytes(1, value); + @$pb.TagNumber(2) + $core.bool hasPublicIdentityKey() => $_has(1); + @$pb.TagNumber(2) + void clearPublicIdentityKey() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get displayName => $_getSZ(2); + @$pb.TagNumber(3) + set displayName($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasDisplayName() => $_has(2); + @$pb.TagNumber(3) + void clearDisplayName() => $_clearField(3); +} + class AdditionalMessageData extends $pb.GeneratedMessage { factory AdditionalMessageData({ AdditionalMessageData_Type? type, $core.String? link, + $core.Iterable? contacts, }) { final result = create(); if (type != null) result.type = type; if (link != null) result.link = link; + if (contacts != null) result.contacts.addAll(contacts); return result; } @@ -49,6 +132,9 @@ class AdditionalMessageData extends $pb.GeneratedMessage { valueOf: AdditionalMessageData_Type.valueOf, enumValues: AdditionalMessageData_Type.values) ..aOS(2, _omitFieldNames ? '' : 'link') + ..pc( + 3, _omitFieldNames ? '' : 'contacts', $pb.PbFieldType.PM, + subBuilder: SharedContact.create) ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -91,6 +177,9 @@ class AdditionalMessageData extends $pb.GeneratedMessage { $core.bool hasLink() => $_has(1); @$pb.TagNumber(2) void clearLink() => $_clearField(2); + + @$pb.TagNumber(3) + $pb.PbList get contacts => $_getList(2); } const $core.bool _omitFieldNames = diff --git a/lib/src/model/protobuf/client/generated/data.pbenum.dart b/lib/src/model/protobuf/client/generated/data.pbenum.dart index 4f40622..22a856c 100644 --- a/lib/src/model/protobuf/client/generated/data.pbenum.dart +++ b/lib/src/model/protobuf/client/generated/data.pbenum.dart @@ -17,14 +17,17 @@ import 'package:protobuf/protobuf.dart' as $pb; class AdditionalMessageData_Type extends $pb.ProtobufEnum { static const AdditionalMessageData_Type LINK = AdditionalMessageData_Type._(0, _omitEnumNames ? '' : 'LINK'); + static const AdditionalMessageData_Type CONTACTS = + AdditionalMessageData_Type._(1, _omitEnumNames ? '' : 'CONTACTS'); static const $core.List values = [ LINK, + CONTACTS, ]; static final $core.List _byValue = - $pb.ProtobufEnum.$_initByValueList(values, 0); + $pb.ProtobufEnum.$_initByValueList(values, 1); static AdditionalMessageData_Type? valueOf($core.int value) => value < 0 || value >= _byValue.length ? null : _byValue[value]; diff --git a/lib/src/model/protobuf/client/generated/data.pbjson.dart b/lib/src/model/protobuf/client/generated/data.pbjson.dart index fb0a248..328a6f7 100644 --- a/lib/src/model/protobuf/client/generated/data.pbjson.dart +++ b/lib/src/model/protobuf/client/generated/data.pbjson.dart @@ -14,6 +14,28 @@ import 'dart:convert' as $convert; import 'dart:core' as $core; import 'dart:typed_data' as $typed_data; +@$core.Deprecated('Use sharedContactDescriptor instead') +const SharedContact$json = { + '1': 'SharedContact', + '2': [ + {'1': 'user_id', '3': 1, '4': 1, '5': 3, '10': 'userId'}, + { + '1': 'public_identity_key', + '3': 2, + '4': 1, + '5': 12, + '10': 'publicIdentityKey' + }, + {'1': 'display_name', '3': 3, '4': 1, '5': 9, '10': 'displayName'}, + ], +}; + +/// Descriptor for `SharedContact`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List sharedContactDescriptor = $convert.base64Decode( + 'Cg1TaGFyZWRDb250YWN0EhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBIuChNwdWJsaWNfaWRlbn' + 'RpdHlfa2V5GAIgASgMUhFwdWJsaWNJZGVudGl0eUtleRIhCgxkaXNwbGF5X25hbWUYAyABKAlS' + 'C2Rpc3BsYXlOYW1l'); + @$core.Deprecated('Use additionalMessageDataDescriptor instead') const AdditionalMessageData$json = { '1': 'AdditionalMessageData', @@ -27,6 +49,14 @@ const AdditionalMessageData$json = { '10': 'type' }, {'1': 'link', '3': 2, '4': 1, '5': 9, '9': 0, '10': 'link', '17': true}, + { + '1': 'contacts', + '3': 3, + '4': 3, + '5': 11, + '6': '.SharedContact', + '10': 'contacts' + }, ], '4': [AdditionalMessageData_Type$json], '8': [ @@ -39,11 +69,13 @@ const AdditionalMessageData_Type$json = { '1': 'Type', '2': [ {'1': 'LINK', '2': 0}, + {'1': 'CONTACTS', '2': 1}, ], }; /// Descriptor for `AdditionalMessageData`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List additionalMessageDataDescriptor = $convert.base64Decode( 'ChVBZGRpdGlvbmFsTWVzc2FnZURhdGESLwoEdHlwZRgBIAEoDjIbLkFkZGl0aW9uYWxNZXNzYW' - 'dlRGF0YS5UeXBlUgR0eXBlEhcKBGxpbmsYAiABKAlIAFIEbGlua4gBASIQCgRUeXBlEggKBExJ' - 'TksQAEIHCgVfbGluaw=='); + 'dlRGF0YS5UeXBlUgR0eXBlEhcKBGxpbmsYAiABKAlIAFIEbGlua4gBARIqCghjb250YWN0cxgD' + 'IAMoCzIOLlNoYXJlZENvbnRhY3RSCGNvbnRhY3RzIh4KBFR5cGUSCAoETElOSxAAEgwKCENPTl' + 'RBQ1RTEAFCBwoFX2xpbms='); diff --git a/lib/src/model/protobuf/client/generated/messages.pb.dart b/lib/src/model/protobuf/client/generated/messages.pb.dart index b137dde..cf8c9e0 100644 --- a/lib/src/model/protobuf/client/generated/messages.pb.dart +++ b/lib/src/model/protobuf/client/generated/messages.pb.dart @@ -767,6 +767,106 @@ class EncryptedContent_TextMessage extends $pb.GeneratedMessage { void clearQuoteMessageId() => $_clearField(4); } +class EncryptedContent_AdditionalDataMessage extends $pb.GeneratedMessage { + factory EncryptedContent_AdditionalDataMessage({ + $core.String? senderMessageId, + $fixnum.Int64? timestamp, + $core.String? type, + $core.List<$core.int>? additionalMessageData, + }) { + final result = create(); + if (senderMessageId != null) result.senderMessageId = senderMessageId; + if (timestamp != null) result.timestamp = timestamp; + if (type != null) result.type = type; + if (additionalMessageData != null) + result.additionalMessageData = additionalMessageData; + return result; + } + + EncryptedContent_AdditionalDataMessage._(); + + factory EncryptedContent_AdditionalDataMessage.fromBuffer( + $core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory EncryptedContent_AdditionalDataMessage.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'EncryptedContent.AdditionalDataMessage', + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'senderMessageId') + ..aInt64(2, _omitFieldNames ? '' : 'timestamp') + ..aOS(3, _omitFieldNames ? '' : 'type') + ..a<$core.List<$core.int>>( + 4, _omitFieldNames ? '' : 'additionalMessageData', $pb.PbFieldType.OY) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EncryptedContent_AdditionalDataMessage clone() => + EncryptedContent_AdditionalDataMessage()..mergeFromMessage(this); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EncryptedContent_AdditionalDataMessage copyWith( + void Function(EncryptedContent_AdditionalDataMessage) updates) => + super.copyWith((message) => + updates(message as EncryptedContent_AdditionalDataMessage)) + as EncryptedContent_AdditionalDataMessage; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static EncryptedContent_AdditionalDataMessage create() => + EncryptedContent_AdditionalDataMessage._(); + @$core.override + EncryptedContent_AdditionalDataMessage createEmptyInstance() => create(); + static $pb.PbList createRepeated() => + $pb.PbList(); + @$core.pragma('dart2js:noInline') + static EncryptedContent_AdditionalDataMessage getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor< + EncryptedContent_AdditionalDataMessage>(create); + static EncryptedContent_AdditionalDataMessage? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get senderMessageId => $_getSZ(0); + @$pb.TagNumber(1) + set senderMessageId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasSenderMessageId() => $_has(0); + @$pb.TagNumber(1) + void clearSenderMessageId() => $_clearField(1); + + @$pb.TagNumber(2) + $fixnum.Int64 get timestamp => $_getI64(1); + @$pb.TagNumber(2) + set timestamp($fixnum.Int64 value) => $_setInt64(1, value); + @$pb.TagNumber(2) + $core.bool hasTimestamp() => $_has(1); + @$pb.TagNumber(2) + void clearTimestamp() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get type => $_getSZ(2); + @$pb.TagNumber(3) + set type($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasType() => $_has(2); + @$pb.TagNumber(3) + void clearType() => $_clearField(3); + + @$pb.TagNumber(4) + $core.List<$core.int> get additionalMessageData => $_getN(3); + @$pb.TagNumber(4) + set additionalMessageData($core.List<$core.int> value) => + $_setBytes(3, value); + @$pb.TagNumber(4) + $core.bool hasAdditionalMessageData() => $_has(3); + @$pb.TagNumber(4) + void clearAdditionalMessageData() => $_clearField(4); +} + class EncryptedContent_Reaction extends $pb.GeneratedMessage { factory EncryptedContent_Reaction({ $core.String? targetMessageId, @@ -1611,6 +1711,7 @@ class EncryptedContent extends $pb.GeneratedMessage { EncryptedContent_GroupUpdate? groupUpdate, EncryptedContent_ResendGroupPublicKey? resendGroupPublicKey, EncryptedContent_ErrorMessages? errorMessages, + EncryptedContent_AdditionalDataMessage? additionalDataMessage, }) { final result = create(); if (groupId != null) result.groupId = groupId; @@ -1632,6 +1733,8 @@ class EncryptedContent extends $pb.GeneratedMessage { if (resendGroupPublicKey != null) result.resendGroupPublicKey = resendGroupPublicKey; if (errorMessages != null) result.errorMessages = errorMessages; + if (additionalDataMessage != null) + result.additionalDataMessage = additionalDataMessage; return result; } @@ -1695,6 +1798,9 @@ class EncryptedContent extends $pb.GeneratedMessage { ..aOM( 18, _omitFieldNames ? '' : 'errorMessages', subBuilder: EncryptedContent_ErrorMessages.create) + ..aOM( + 19, _omitFieldNames ? '' : 'additionalDataMessage', + subBuilder: EncryptedContent_AdditionalDataMessage.create) ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -1905,6 +2011,20 @@ class EncryptedContent extends $pb.GeneratedMessage { void clearErrorMessages() => $_clearField(18); @$pb.TagNumber(18) EncryptedContent_ErrorMessages ensureErrorMessages() => $_ensure(16); + + @$pb.TagNumber(19) + EncryptedContent_AdditionalDataMessage get additionalDataMessage => + $_getN(17); + @$pb.TagNumber(19) + set additionalDataMessage(EncryptedContent_AdditionalDataMessage value) => + $_setField(19, value); + @$pb.TagNumber(19) + $core.bool hasAdditionalDataMessage() => $_has(17); + @$pb.TagNumber(19) + void clearAdditionalDataMessage() => $_clearField(19); + @$pb.TagNumber(19) + EncryptedContent_AdditionalDataMessage ensureAdditionalDataMessage() => + $_ensure(17); } const $core.bool _omitFieldNames = diff --git a/lib/src/model/protobuf/client/generated/messages.pbjson.dart b/lib/src/model/protobuf/client/generated/messages.pbjson.dart index fd49512..ac60f8f 100644 --- a/lib/src/model/protobuf/client/generated/messages.pbjson.dart +++ b/lib/src/model/protobuf/client/generated/messages.pbjson.dart @@ -316,6 +316,16 @@ const EncryptedContent$json = { '10': 'errorMessages', '17': true }, + { + '1': 'additional_data_message', + '3': 19, + '4': 1, + '5': 11, + '6': '.EncryptedContent.AdditionalDataMessage', + '9': 17, + '10': 'additionalDataMessage', + '17': true + }, ], '3': [ EncryptedContent_ErrorMessages$json, @@ -324,6 +334,7 @@ const EncryptedContent$json = { EncryptedContent_ResendGroupPublicKey$json, EncryptedContent_GroupUpdate$json, EncryptedContent_TextMessage$json, + EncryptedContent_AdditionalDataMessage$json, EncryptedContent_Reaction$json, EncryptedContent_MessageUpdate$json, EncryptedContent_Media$json, @@ -351,6 +362,7 @@ const EncryptedContent$json = { {'1': '_groupUpdate'}, {'1': '_resendGroupPublicKey'}, {'1': '_error_messages'}, + {'1': '_additional_data_message'}, ], }; @@ -470,6 +482,28 @@ const EncryptedContent_TextMessage$json = { ], }; +@$core.Deprecated('Use encryptedContentDescriptor instead') +const EncryptedContent_AdditionalDataMessage$json = { + '1': 'AdditionalDataMessage', + '2': [ + {'1': 'sender_message_id', '3': 1, '4': 1, '5': 9, '10': 'senderMessageId'}, + {'1': 'timestamp', '3': 2, '4': 1, '5': 3, '10': 'timestamp'}, + {'1': 'type', '3': 3, '4': 1, '5': 9, '10': 'type'}, + { + '1': 'additional_message_data', + '3': 4, + '4': 1, + '5': 12, + '9': 0, + '10': 'additionalMessageData', + '17': true + }, + ], + '8': [ + {'1': '_additional_message_data'}, + ], +}; + @$core.Deprecated('Use encryptedContentDescriptor instead') const EncryptedContent_Reaction$json = { '1': 'Reaction', @@ -827,63 +861,69 @@ final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode( '5SC2dyb3VwVXBkYXRliAEBEl8KFHJlc2VuZEdyb3VwUHVibGljS2V5GBEgASgLMiYuRW5jcnlw' 'dGVkQ29udGVudC5SZXNlbmRHcm91cFB1YmxpY0tleUgPUhRyZXNlbmRHcm91cFB1YmxpY0tleY' 'gBARJLCg5lcnJvcl9tZXNzYWdlcxgSIAEoCzIfLkVuY3J5cHRlZENvbnRlbnQuRXJyb3JNZXNz' - 'YWdlc0gQUg1lcnJvck1lc3NhZ2VziAEBGtcBCg1FcnJvck1lc3NhZ2VzEjgKBHR5cGUYASABKA' - '4yJC5FbmNyeXB0ZWRDb250ZW50LkVycm9yTWVzc2FnZXMuVHlwZVIEdHlwZRIsChJyZWxhdGVk' - 'X3JlY2VpcHRfaWQYAiABKAlSEHJlbGF0ZWRSZWNlaXB0SWQiXgoEVHlwZRI8CjhFUlJPUl9QUk' - '9DRVNTSU5HX01FU1NBR0VfQ1JFQVRFRF9BQ0NPVU5UX1JFUVVFU1RfSU5TVEVBRBAAEhgKFFVO' - 'S05PV05fTUVTU0FHRV9UWVBFEAIaUQoLR3JvdXBDcmVhdGUSGgoIc3RhdGVLZXkYAyABKAxSCH' - 'N0YXRlS2V5EiYKDmdyb3VwUHVibGljS2V5GAQgASgMUg5ncm91cFB1YmxpY0tleRozCglHcm91' - 'cEpvaW4SJgoOZ3JvdXBQdWJsaWNLZXkYASABKAxSDmdyb3VwUHVibGljS2V5GhYKFFJlc2VuZE' - 'dyb3VwUHVibGljS2V5GrYCCgtHcm91cFVwZGF0ZRIoCg9ncm91cEFjdGlvblR5cGUYASABKAlS' - 'D2dyb3VwQWN0aW9uVHlwZRIxChFhZmZlY3RlZENvbnRhY3RJZBgCIAEoA0gAUhFhZmZlY3RlZE' - 'NvbnRhY3RJZIgBARInCgxuZXdHcm91cE5hbWUYAyABKAlIAVIMbmV3R3JvdXBOYW1liAEBElMK' - 'Im5ld0RlbGV0ZU1lc3NhZ2VzQWZ0ZXJNaWxsaXNlY29uZHMYBCABKANIAlIibmV3RGVsZXRlTW' - 'Vzc2FnZXNBZnRlck1pbGxpc2Vjb25kc4gBAUIUChJfYWZmZWN0ZWRDb250YWN0SWRCDwoNX25l' - 'd0dyb3VwTmFtZUIlCiNfbmV3RGVsZXRlTWVzc2FnZXNBZnRlck1pbGxpc2Vjb25kcxqpAQoLVG' - 'V4dE1lc3NhZ2USKAoPc2VuZGVyTWVzc2FnZUlkGAEgASgJUg9zZW5kZXJNZXNzYWdlSWQSEgoE' - 'dGV4dBgCIAEoCVIEdGV4dBIcCgl0aW1lc3RhbXAYAyABKANSCXRpbWVzdGFtcBIrCg5xdW90ZU' - '1lc3NhZ2VJZBgEIAEoCUgAUg5xdW90ZU1lc3NhZ2VJZIgBAUIRCg9fcXVvdGVNZXNzYWdlSWQa' - 'YgoIUmVhY3Rpb24SKAoPdGFyZ2V0TWVzc2FnZUlkGAEgASgJUg90YXJnZXRNZXNzYWdlSWQSFA' - 'oFZW1vamkYAiABKAlSBWVtb2ppEhYKBnJlbW92ZRgDIAEoCFIGcmVtb3ZlGrcCCg1NZXNzYWdl' - 'VXBkYXRlEjgKBHR5cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50Lk1lc3NhZ2VVcGRhdGUuVH' - 'lwZVIEdHlwZRItCg9zZW5kZXJNZXNzYWdlSWQYAiABKAlIAFIPc2VuZGVyTWVzc2FnZUlkiAEB' - 'EjoKGG11bHRpcGxlVGFyZ2V0TWVzc2FnZUlkcxgDIAMoCVIYbXVsdGlwbGVUYXJnZXRNZXNzYW' - 'dlSWRzEhcKBHRleHQYBCABKAlIAVIEdGV4dIgBARIcCgl0aW1lc3RhbXAYBSABKANSCXRpbWVz' - 'dGFtcCItCgRUeXBlEgoKBkRFTEVURRAAEg0KCUVESVRfVEVYVBABEgoKBk9QRU5FRBACQhIKEF' - '9zZW5kZXJNZXNzYWdlSWRCBwoFX3RleHQa8AUKBU1lZGlhEigKD3NlbmRlck1lc3NhZ2VJZBgB' - 'IAEoCVIPc2VuZGVyTWVzc2FnZUlkEjAKBHR5cGUYAiABKA4yHC5FbmNyeXB0ZWRDb250ZW50Lk' - '1lZGlhLlR5cGVSBHR5cGUSQwoaZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHMYAyABKANIAFIa' - 'ZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHOIAQESNgoWcmVxdWlyZXNBdXRoZW50aWNhdGlvbh' - 'gEIAEoCFIWcmVxdWlyZXNBdXRoZW50aWNhdGlvbhIcCgl0aW1lc3RhbXAYBSABKANSCXRpbWVz' - 'dGFtcBIrCg5xdW90ZU1lc3NhZ2VJZBgGIAEoCUgBUg5xdW90ZU1lc3NhZ2VJZIgBARIpCg1kb3' - 'dubG9hZFRva2VuGAcgASgMSAJSDWRvd25sb2FkVG9rZW6IAQESKQoNZW5jcnlwdGlvbktleRgI' - 'IAEoDEgDUg1lbmNyeXB0aW9uS2V5iAEBEikKDWVuY3J5cHRpb25NYWMYCSABKAxIBFINZW5jcn' - 'lwdGlvbk1hY4gBARItCg9lbmNyeXB0aW9uTm9uY2UYCiABKAxIBVIPZW5jcnlwdGlvbk5vbmNl' - 'iAEBEjsKF2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhGAsgASgMSAZSFWFkZGl0aW9uYWxNZXNzYW' - 'dlRGF0YYgBASI+CgRUeXBlEgwKCFJFVVBMT0FEEAASCQoFSU1BR0UQARIJCgVWSURFTxACEgcK' - 'A0dJRhADEgkKBUFVRElPEARCHQobX2Rpc3BsYXlMaW1pdEluTWlsbGlzZWNvbmRzQhEKD19xdW' - '90ZU1lc3NhZ2VJZEIQCg5fZG93bmxvYWRUb2tlbkIQCg5fZW5jcnlwdGlvbktleUIQCg5fZW5j' - 'cnlwdGlvbk1hY0ISChBfZW5jcnlwdGlvbk5vbmNlQhoKGF9hZGRpdGlvbmFsX21lc3NhZ2VfZG' - 'F0YRqnAQoLTWVkaWFVcGRhdGUSNgoEdHlwZRgBIAEoDjIiLkVuY3J5cHRlZENvbnRlbnQuTWVk' - 'aWFVcGRhdGUuVHlwZVIEdHlwZRIoCg90YXJnZXRNZXNzYWdlSWQYAiABKAlSD3RhcmdldE1lc3' - 'NhZ2VJZCI2CgRUeXBlEgwKCFJFT1BFTkVEEAASCgoGU1RPUkVEEAESFAoQREVDUllQVElPTl9F' - 'UlJPUhACGngKDkNvbnRhY3RSZXF1ZXN0EjkKBHR5cGUYASABKA4yJS5FbmNyeXB0ZWRDb250ZW' - '50LkNvbnRhY3RSZXF1ZXN0LlR5cGVSBHR5cGUiKwoEVHlwZRILCgdSRVFVRVNUEAASCgoGUkVK' - 'RUNUEAESCgoGQUNDRVBUEAIangIKDUNvbnRhY3RVcGRhdGUSOAoEdHlwZRgBIAEoDjIkLkVuY3' - 'J5cHRlZENvbnRlbnQuQ29udGFjdFVwZGF0ZS5UeXBlUgR0eXBlEjUKE2F2YXRhclN2Z0NvbXBy' - 'ZXNzZWQYAiABKAxIAFITYXZhdGFyU3ZnQ29tcHJlc3NlZIgBARIfCgh1c2VybmFtZRgDIAEoCU' - 'gBUgh1c2VybmFtZYgBARIlCgtkaXNwbGF5TmFtZRgEIAEoCUgCUgtkaXNwbGF5TmFtZYgBASIf' - 'CgRUeXBlEgsKB1JFUVVFU1QQABIKCgZVUERBVEUQAUIWChRfYXZhdGFyU3ZnQ29tcHJlc3NlZE' - 'ILCglfdXNlcm5hbWVCDgoMX2Rpc3BsYXlOYW1lGtUBCghQdXNoS2V5cxIzCgR0eXBlGAEgASgO' - 'Mh8uRW5jcnlwdGVkQ29udGVudC5QdXNoS2V5cy5UeXBlUgR0eXBlEhkKBWtleUlkGAIgASgDSA' - 'BSBWtleUlkiAEBEhUKA2tleRgDIAEoDEgBUgNrZXmIAQESIQoJY3JlYXRlZEF0GAQgASgDSAJS' - 'CWNyZWF0ZWRBdIgBASIfCgRUeXBlEgsKB1JFUVVFU1QQABIKCgZVUERBVEUQAUIICgZfa2V5SW' - 'RCBgoEX2tleUIMCgpfY3JlYXRlZEF0GqkBCglGbGFtZVN5bmMSIgoMZmxhbWVDb3VudGVyGAEg' - 'ASgDUgxmbGFtZUNvdW50ZXISNgoWbGFzdEZsYW1lQ291bnRlckNoYW5nZRgCIAEoA1IWbGFzdE' - 'ZsYW1lQ291bnRlckNoYW5nZRIeCgpiZXN0RnJpZW5kGAMgASgIUgpiZXN0RnJpZW5kEiAKC2Zv' - 'cmNlVXBkYXRlGAQgASgIUgtmb3JjZVVwZGF0ZUIKCghfZ3JvdXBJZEIPCg1faXNEaXJlY3RDaG' - 'F0QhcKFV9zZW5kZXJQcm9maWxlQ291bnRlckIQCg5fbWVzc2FnZVVwZGF0ZUIICgZfbWVkaWFC' - 'DgoMX21lZGlhVXBkYXRlQhAKDl9jb250YWN0VXBkYXRlQhEKD19jb250YWN0UmVxdWVzdEIMCg' - 'pfZmxhbWVTeW5jQgsKCV9wdXNoS2V5c0ILCglfcmVhY3Rpb25CDgoMX3RleHRNZXNzYWdlQg4K' - 'DF9ncm91cENyZWF0ZUIMCgpfZ3JvdXBKb2luQg4KDF9ncm91cFVwZGF0ZUIXChVfcmVzZW5kR3' - 'JvdXBQdWJsaWNLZXlCEQoPX2Vycm9yX21lc3NhZ2Vz'); + 'YWdlc0gQUg1lcnJvck1lc3NhZ2VziAEBEmQKF2FkZGl0aW9uYWxfZGF0YV9tZXNzYWdlGBMgAS' + 'gLMicuRW5jcnlwdGVkQ29udGVudC5BZGRpdGlvbmFsRGF0YU1lc3NhZ2VIEVIVYWRkaXRpb25h' + 'bERhdGFNZXNzYWdliAEBGtcBCg1FcnJvck1lc3NhZ2VzEjgKBHR5cGUYASABKA4yJC5FbmNyeX' + 'B0ZWRDb250ZW50LkVycm9yTWVzc2FnZXMuVHlwZVIEdHlwZRIsChJyZWxhdGVkX3JlY2VpcHRf' + 'aWQYAiABKAlSEHJlbGF0ZWRSZWNlaXB0SWQiXgoEVHlwZRI8CjhFUlJPUl9QUk9DRVNTSU5HX0' + '1FU1NBR0VfQ1JFQVRFRF9BQ0NPVU5UX1JFUVVFU1RfSU5TVEVBRBAAEhgKFFVOS05PV05fTUVT' + 'U0FHRV9UWVBFEAIaUQoLR3JvdXBDcmVhdGUSGgoIc3RhdGVLZXkYAyABKAxSCHN0YXRlS2V5Ei' + 'YKDmdyb3VwUHVibGljS2V5GAQgASgMUg5ncm91cFB1YmxpY0tleRozCglHcm91cEpvaW4SJgoO' + 'Z3JvdXBQdWJsaWNLZXkYASABKAxSDmdyb3VwUHVibGljS2V5GhYKFFJlc2VuZEdyb3VwUHVibG' + 'ljS2V5GrYCCgtHcm91cFVwZGF0ZRIoCg9ncm91cEFjdGlvblR5cGUYASABKAlSD2dyb3VwQWN0' + 'aW9uVHlwZRIxChFhZmZlY3RlZENvbnRhY3RJZBgCIAEoA0gAUhFhZmZlY3RlZENvbnRhY3RJZI' + 'gBARInCgxuZXdHcm91cE5hbWUYAyABKAlIAVIMbmV3R3JvdXBOYW1liAEBElMKIm5ld0RlbGV0' + 'ZU1lc3NhZ2VzQWZ0ZXJNaWxsaXNlY29uZHMYBCABKANIAlIibmV3RGVsZXRlTWVzc2FnZXNBZn' + 'Rlck1pbGxpc2Vjb25kc4gBAUIUChJfYWZmZWN0ZWRDb250YWN0SWRCDwoNX25ld0dyb3VwTmFt' + 'ZUIlCiNfbmV3RGVsZXRlTWVzc2FnZXNBZnRlck1pbGxpc2Vjb25kcxqpAQoLVGV4dE1lc3NhZ2' + 'USKAoPc2VuZGVyTWVzc2FnZUlkGAEgASgJUg9zZW5kZXJNZXNzYWdlSWQSEgoEdGV4dBgCIAEo' + 'CVIEdGV4dBIcCgl0aW1lc3RhbXAYAyABKANSCXRpbWVzdGFtcBIrCg5xdW90ZU1lc3NhZ2VJZB' + 'gEIAEoCUgAUg5xdW90ZU1lc3NhZ2VJZIgBAUIRCg9fcXVvdGVNZXNzYWdlSWQazgEKFUFkZGl0' + 'aW9uYWxEYXRhTWVzc2FnZRIqChFzZW5kZXJfbWVzc2FnZV9pZBgBIAEoCVIPc2VuZGVyTWVzc2' + 'FnZUlkEhwKCXRpbWVzdGFtcBgCIAEoA1IJdGltZXN0YW1wEhIKBHR5cGUYAyABKAlSBHR5cGUS' + 'OwoXYWRkaXRpb25hbF9tZXNzYWdlX2RhdGEYBCABKAxIAFIVYWRkaXRpb25hbE1lc3NhZ2VEYX' + 'RhiAEBQhoKGF9hZGRpdGlvbmFsX21lc3NhZ2VfZGF0YRpiCghSZWFjdGlvbhIoCg90YXJnZXRN' + 'ZXNzYWdlSWQYASABKAlSD3RhcmdldE1lc3NhZ2VJZBIUCgVlbW9qaRgCIAEoCVIFZW1vamkSFg' + 'oGcmVtb3ZlGAMgASgIUgZyZW1vdmUatwIKDU1lc3NhZ2VVcGRhdGUSOAoEdHlwZRgBIAEoDjIk' + 'LkVuY3J5cHRlZENvbnRlbnQuTWVzc2FnZVVwZGF0ZS5UeXBlUgR0eXBlEi0KD3NlbmRlck1lc3' + 'NhZ2VJZBgCIAEoCUgAUg9zZW5kZXJNZXNzYWdlSWSIAQESOgoYbXVsdGlwbGVUYXJnZXRNZXNz' + 'YWdlSWRzGAMgAygJUhhtdWx0aXBsZVRhcmdldE1lc3NhZ2VJZHMSFwoEdGV4dBgEIAEoCUgBUg' + 'R0ZXh0iAEBEhwKCXRpbWVzdGFtcBgFIAEoA1IJdGltZXN0YW1wIi0KBFR5cGUSCgoGREVMRVRF' + 'EAASDQoJRURJVF9URVhUEAESCgoGT1BFTkVEEAJCEgoQX3NlbmRlck1lc3NhZ2VJZEIHCgVfdG' + 'V4dBrwBQoFTWVkaWESKAoPc2VuZGVyTWVzc2FnZUlkGAEgASgJUg9zZW5kZXJNZXNzYWdlSWQS' + 'MAoEdHlwZRgCIAEoDjIcLkVuY3J5cHRlZENvbnRlbnQuTWVkaWEuVHlwZVIEdHlwZRJDChpkaX' + 'NwbGF5TGltaXRJbk1pbGxpc2Vjb25kcxgDIAEoA0gAUhpkaXNwbGF5TGltaXRJbk1pbGxpc2Vj' + 'b25kc4gBARI2ChZyZXF1aXJlc0F1dGhlbnRpY2F0aW9uGAQgASgIUhZyZXF1aXJlc0F1dGhlbn' + 'RpY2F0aW9uEhwKCXRpbWVzdGFtcBgFIAEoA1IJdGltZXN0YW1wEisKDnF1b3RlTWVzc2FnZUlk' + 'GAYgASgJSAFSDnF1b3RlTWVzc2FnZUlkiAEBEikKDWRvd25sb2FkVG9rZW4YByABKAxIAlINZG' + '93bmxvYWRUb2tlbogBARIpCg1lbmNyeXB0aW9uS2V5GAggASgMSANSDWVuY3J5cHRpb25LZXmI' + 'AQESKQoNZW5jcnlwdGlvbk1hYxgJIAEoDEgEUg1lbmNyeXB0aW9uTWFjiAEBEi0KD2VuY3J5cH' + 'Rpb25Ob25jZRgKIAEoDEgFUg9lbmNyeXB0aW9uTm9uY2WIAQESOwoXYWRkaXRpb25hbF9tZXNz' + 'YWdlX2RhdGEYCyABKAxIBlIVYWRkaXRpb25hbE1lc3NhZ2VEYXRhiAEBIj4KBFR5cGUSDAoIUk' + 'VVUExPQUQQABIJCgVJTUFHRRABEgkKBVZJREVPEAISBwoDR0lGEAMSCQoFQVVESU8QBEIdChtf' + 'ZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHNCEQoPX3F1b3RlTWVzc2FnZUlkQhAKDl9kb3dubG' + '9hZFRva2VuQhAKDl9lbmNyeXB0aW9uS2V5QhAKDl9lbmNyeXB0aW9uTWFjQhIKEF9lbmNyeXB0' + 'aW9uTm9uY2VCGgoYX2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhGqcBCgtNZWRpYVVwZGF0ZRI2Cg' + 'R0eXBlGAEgASgOMiIuRW5jcnlwdGVkQ29udGVudC5NZWRpYVVwZGF0ZS5UeXBlUgR0eXBlEigK' + 'D3RhcmdldE1lc3NhZ2VJZBgCIAEoCVIPdGFyZ2V0TWVzc2FnZUlkIjYKBFR5cGUSDAoIUkVPUE' + 'VORUQQABIKCgZTVE9SRUQQARIUChBERUNSWVBUSU9OX0VSUk9SEAIaeAoOQ29udGFjdFJlcXVl' + 'c3QSOQoEdHlwZRgBIAEoDjIlLkVuY3J5cHRlZENvbnRlbnQuQ29udGFjdFJlcXVlc3QuVHlwZV' + 'IEdHlwZSIrCgRUeXBlEgsKB1JFUVVFU1QQABIKCgZSRUpFQ1QQARIKCgZBQ0NFUFQQAhqeAgoN' + 'Q29udGFjdFVwZGF0ZRI4CgR0eXBlGAEgASgOMiQuRW5jcnlwdGVkQ29udGVudC5Db250YWN0VX' + 'BkYXRlLlR5cGVSBHR5cGUSNQoTYXZhdGFyU3ZnQ29tcHJlc3NlZBgCIAEoDEgAUhNhdmF0YXJT' + 'dmdDb21wcmVzc2VkiAEBEh8KCHVzZXJuYW1lGAMgASgJSAFSCHVzZXJuYW1liAEBEiUKC2Rpc3' + 'BsYXlOYW1lGAQgASgJSAJSC2Rpc3BsYXlOYW1liAEBIh8KBFR5cGUSCwoHUkVRVUVTVBAAEgoK' + 'BlVQREFURRABQhYKFF9hdmF0YXJTdmdDb21wcmVzc2VkQgsKCV91c2VybmFtZUIOCgxfZGlzcG' + 'xheU5hbWUa1QEKCFB1c2hLZXlzEjMKBHR5cGUYASABKA4yHy5FbmNyeXB0ZWRDb250ZW50LlB1' + 'c2hLZXlzLlR5cGVSBHR5cGUSGQoFa2V5SWQYAiABKANIAFIFa2V5SWSIAQESFQoDa2V5GAMgAS' + 'gMSAFSA2tleYgBARIhCgljcmVhdGVkQXQYBCABKANIAlIJY3JlYXRlZEF0iAEBIh8KBFR5cGUS' + 'CwoHUkVRVUVTVBAAEgoKBlVQREFURRABQggKBl9rZXlJZEIGCgRfa2V5QgwKCl9jcmVhdGVkQX' + 'QaqQEKCUZsYW1lU3luYxIiCgxmbGFtZUNvdW50ZXIYASABKANSDGZsYW1lQ291bnRlchI2ChZs' + 'YXN0RmxhbWVDb3VudGVyQ2hhbmdlGAIgASgDUhZsYXN0RmxhbWVDb3VudGVyQ2hhbmdlEh4KCm' + 'Jlc3RGcmllbmQYAyABKAhSCmJlc3RGcmllbmQSIAoLZm9yY2VVcGRhdGUYBCABKAhSC2ZvcmNl' + 'VXBkYXRlQgoKCF9ncm91cElkQg8KDV9pc0RpcmVjdENoYXRCFwoVX3NlbmRlclByb2ZpbGVDb3' + 'VudGVyQhAKDl9tZXNzYWdlVXBkYXRlQggKBl9tZWRpYUIOCgxfbWVkaWFVcGRhdGVCEAoOX2Nv' + 'bnRhY3RVcGRhdGVCEQoPX2NvbnRhY3RSZXF1ZXN0QgwKCl9mbGFtZVN5bmNCCwoJX3B1c2hLZX' + 'lzQgsKCV9yZWFjdGlvbkIOCgxfdGV4dE1lc3NhZ2VCDgoMX2dyb3VwQ3JlYXRlQgwKCl9ncm91' + 'cEpvaW5CDgoMX2dyb3VwVXBkYXRlQhcKFV9yZXNlbmRHcm91cFB1YmxpY0tleUIRCg9fZXJyb3' + 'JfbWVzc2FnZXNCGgoYX2FkZGl0aW9uYWxfZGF0YV9tZXNzYWdl'); diff --git a/lib/src/model/protobuf/client/messages.proto b/lib/src/model/protobuf/client/messages.proto index 38e24ad..062fc6f 100644 --- a/lib/src/model/protobuf/client/messages.proto +++ b/lib/src/model/protobuf/client/messages.proto @@ -52,7 +52,7 @@ message EncryptedContent { optional GroupUpdate groupUpdate = 16; optional ResendGroupPublicKey resendGroupPublicKey = 17; optional ErrorMessages error_messages = 18; - + optional AdditionalDataMessage additional_data_message = 19; message ErrorMessages { enum Type { @@ -93,6 +93,13 @@ message EncryptedContent { optional string quoteMessageId = 4; } + message AdditionalDataMessage { + string sender_message_id = 1; + int64 timestamp = 2; + string type = 3; + optional bytes additional_message_data = 4; + } + message Reaction { string targetMessageId = 1; string emoji = 2; diff --git a/lib/src/services/api/client2client/additional_data.c2c.dart b/lib/src/services/api/client2client/additional_data.c2c.dart new file mode 100644 index 0000000..fdea032 --- /dev/null +++ b/lib/src/services/api/client2client/additional_data.c2c.dart @@ -0,0 +1,36 @@ +import 'package:clock/clock.dart' show clock; +import 'package:drift/drift.dart'; +import 'package:twonly/globals.dart'; +import 'package:twonly/src/database/twonly.db.dart'; +import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; +import 'package:twonly/src/services/api/utils.dart'; +import 'package:twonly/src/utils/log.dart'; + +Future handleAdditionalDataMessage( + int fromUserId, + String groupId, + EncryptedContent_AdditionalDataMessage message, +) async { + Log.info( + 'Got a additional data message: ${message.senderMessageId} from $groupId', + ); + final msg = await twonlyDB.messagesDao.insertMessage( + MessagesCompanion( + messageId: Value(message.senderMessageId), + senderId: Value(fromUserId), + groupId: Value(groupId), + type: Value(message.type), + additionalMessageData: + Value(Uint8List.fromList(message.additionalMessageData)), + createdAt: Value(fromTimestamp(message.timestamp)), + ackByServer: Value(clock.now()), + ), + ); + await twonlyDB.groupsDao.increaseLastMessageExchange( + groupId, + fromTimestamp(message.timestamp), + ); + if (msg != null) { + Log.info('Inserted a new text message with ID: ${msg.messageId}'); + } +} diff --git a/lib/src/services/api/client2client/media.c2c.dart b/lib/src/services/api/client2client/media.c2c.dart index 4cef307..9ea1886 100644 --- a/lib/src/services/api/client2client/media.c2c.dart +++ b/lib/src/services/api/client2client/media.c2c.dart @@ -116,7 +116,7 @@ Future handleMedia( senderId: Value(fromUserId), groupId: Value(groupId), mediaId: Value(mediaFile.mediaId), - type: const Value(MessageType.media), + type: Value(MessageType.media.name), additionalMessageData: Value.absentIfNull( media.hasAdditionalMessageData() ? Uint8List.fromList(media.additionalMessageData) diff --git a/lib/src/services/api/client2client/text_message.c2c.dart b/lib/src/services/api/client2client/text_message.c2c.dart index 2a9b301..a189f39 100644 --- a/lib/src/services/api/client2client/text_message.c2c.dart +++ b/lib/src/services/api/client2client/text_message.c2c.dart @@ -22,7 +22,7 @@ Future handleTextMessage( senderId: Value(fromUserId), groupId: Value(groupId), content: Value(textMessage.text), - type: const Value(MessageType.text), + type: Value(MessageType.text.name), quotesMessageId: Value( textMessage.hasQuoteMessageId() ? textMessage.quoteMessageId : null, ), diff --git a/lib/src/services/api/mediafiles/download.service.dart b/lib/src/services/api/mediafiles/download.service.dart index 75368ab..6782e3b 100644 --- a/lib/src/services/api/mediafiles/download.service.dart +++ b/lib/src/services/api/mediafiles/download.service.dart @@ -5,7 +5,6 @@ import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart'; import 'package:cryptography_plus/cryptography_plus.dart'; import 'package:drift/drift.dart'; -import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:mutex/mutex.dart'; import 'package:path/path.dart'; diff --git a/lib/src/services/api/mediafiles/upload.service.dart b/lib/src/services/api/mediafiles/upload.service.dart index 7dd4f20..6e06a2c 100644 --- a/lib/src/services/api/mediafiles/upload.service.dart +++ b/lib/src/services/api/mediafiles/upload.service.dart @@ -102,7 +102,7 @@ Future insertMediaFileInMessagesTable( MessagesCompanion( groupId: Value(groupId), mediaId: Value(mediaService.mediaFile.mediaId), - type: const Value(MessageType.media), + type: Value(MessageType.media.name), additionalMessageData: Value.absentIfNull(additionalData?.writeToBuffer()), ), diff --git a/lib/src/services/api/messages.dart b/lib/src/services/api/messages.dart index c92bdab..dd83859 100644 --- a/lib/src/services/api/messages.dart +++ b/lib/src/services/api/messages.dart @@ -7,14 +7,17 @@ import 'package:fixnum/fixnum.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import 'package:mutex/mutex.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/tables/messages.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart'; +import 'package:twonly/src/model/protobuf/client/generated/data.pb.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart' as pb; import 'package:twonly/src/model/protobuf/client/generated/push_notification.pb.dart'; import 'package:twonly/src/services/notifications/pushkeys.notifications.dart'; import 'package:twonly/src/services/signal/encryption.signal.dart'; +import 'package:twonly/src/services/signal/session.signal.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; @@ -204,7 +207,7 @@ Future insertAndSendTextMessage( MessagesCompanion( groupId: Value(groupId), content: Value(textMessage), - type: const Value(MessageType.text), + type: Value(MessageType.text.name), quotesMessageId: Value(quotesMessageId), ), ); @@ -232,6 +235,61 @@ Future insertAndSendTextMessage( ); } +Future insertAndSendContactShareMessage( + String groupId, + List contactsToShare, +) async { + final contacts = []; + + for (final contactId in contactsToShare) { + final contact = await twonlyDB.contactsDao.getContactById(contactId); + if (contact != null) { + final publicIdentityKey = await getPublicKeyFromContact(contactId); + + contacts.add( + SharedContact( + userId: Int64(contact.userId), + publicIdentityKey: publicIdentityKey, + displayName: getContactDisplayName(contact), + ), + ); + } + } + + final additionalMessageData = AdditionalMessageData( + type: AdditionalMessageData_Type.CONTACTS, + contacts: contacts, + ); + + final message = await twonlyDB.messagesDao.insertMessage( + MessagesCompanion( + groupId: Value(groupId), + type: Value(MessageType.contacts.name), + additionalMessageData: Value(additionalMessageData.writeToBuffer()), + ), + ); + + if (message == null) { + Log.error('Could not insert message into database'); + return; + } + + final encryptedContent = pb.EncryptedContent( + additionalDataMessage: pb.EncryptedContent_AdditionalDataMessage( + senderMessageId: message.messageId, + additionalMessageData: additionalMessageData.writeToBuffer(), + timestamp: Int64(message.createdAt.millisecondsSinceEpoch), + type: MessageType.contacts.name, + ), + ); + + await sendCipherTextToGroup( + groupId, + encryptedContent, + messageId: message.messageId, + ); +} + Future sendCipherTextToGroup( String groupId, pb.EncryptedContent encryptedContent, { diff --git a/lib/src/services/api/server_messages.dart b/lib/src/services/api/server_messages.dart index 2a0546f..7ca7f0d 100644 --- a/lib/src/services/api/server_messages.dart +++ b/lib/src/services/api/server_messages.dart @@ -13,6 +13,7 @@ import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart as server; import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pbserver.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; +import 'package:twonly/src/services/api/client2client/additional_data.c2c.dart'; import 'package:twonly/src/services/api/client2client/contact.c2c.dart'; import 'package:twonly/src/services/api/client2client/errors.c2c.dart'; import 'package:twonly/src/services/api/client2client/groups.c2c.dart'; @@ -374,6 +375,15 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage( return (null, null); } + if (content.hasAdditionalDataMessage()) { + await handleAdditionalDataMessage( + fromUserId, + content.groupId, + content.additionalDataMessage, + ); + return (null, null); + } + if (content.hasTextMessage()) { await handleTextMessage( fromUserId, diff --git a/lib/src/services/api/utils.dart b/lib/src/services/api/utils.dart index 7357d06..facc198 100644 --- a/lib/src/services/api/utils.dart +++ b/lib/src/services/api/utils.dart @@ -12,6 +12,8 @@ import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dart' hide Message; import 'package:twonly/src/services/api/messages.dart'; +import 'package:twonly/src/services/notifications/pushkeys.notifications.dart'; +import 'package:twonly/src/services/signal/session.signal.dart'; class Result { Result.error(this.error) : value = null; @@ -78,3 +80,23 @@ Future handleMediaError(MediaFile media) async { ), ); } + +Future importSignalContactAndCreateRequest( + server.Response_UserData userdata, +) async { + if (await createNewSignalSession(userdata)) { + // 1. Setup notifications keys with the other user + await setupNotificationWithUsers( + forceContact: userdata.userId.toInt(), + ); + // 2. Then send user request + await sendCipherText( + userdata.userId.toInt(), + EncryptedContent( + contactRequest: EncryptedContent_ContactRequest( + type: EncryptedContent_ContactRequest_Type.REQUEST, + ), + ), + ); + } +} diff --git a/lib/src/services/notifications/pushkeys.notifications.dart b/lib/src/services/notifications/pushkeys.notifications.dart index 062b87c..dd319b8 100644 --- a/lib/src/services/notifications/pushkeys.notifications.dart +++ b/lib/src/services/notifications/pushkeys.notifications.dart @@ -242,6 +242,11 @@ Future getPushNotificationFromEncryptedContent( additionalContent = group.groupName; } } + + if (content.hasAdditionalDataMessage()) { + kind = PushKind.text; + } + if (content.hasMedia()) { switch (content.media.type) { case EncryptedContent_Media_Type.REUPLOAD: diff --git a/lib/src/utils/misc.dart b/lib/src/utils/misc.dart index 2db2272..e398fac 100644 --- a/lib/src/utils/misc.dart +++ b/lib/src/utils/misc.dart @@ -24,6 +24,16 @@ extension ShortCutsExtension on BuildContext { AppLocalizations get lang => AppLocalizations.of(this)!; TwonlyDB get db => Provider.of(this); ColorScheme get color => Theme.of(this).colorScheme; + Future navPush(Widget route) async { + return Navigator.push( + this, + MaterialPageRoute( + builder: (context) { + return route; + }, + ), + ); + } } Future saveImageToGallery(Uint8List imageBytes) async { @@ -292,7 +302,7 @@ Color getMessageColorFromType( ) { Color color; - if (message.type == MessageType.text) { + if (message.type == MessageType.text.name) { color = Colors.blueAccent; } else if (mediaFile != null) { if (mediaFile.requiresAuthentication) { diff --git a/lib/src/utils/qr.dart b/lib/src/utils/qr.dart index 5b2df48..331b00f 100644 --- a/lib/src/utils/qr.dart +++ b/lib/src/utils/qr.dart @@ -4,12 +4,9 @@ import 'package:flutter/foundation.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'; -import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; import 'package:twonly/src/model/protobuf/client/generated/qr.pb.dart'; -import 'package:twonly/src/services/api/messages.dart'; -import 'package:twonly/src/services/notifications/pushkeys.notifications.dart'; +import 'package:twonly/src/services/api/utils.dart'; import 'package:twonly/src/services/signal/identity.signal.dart'; -import 'package:twonly/src/services/signal/session.signal.dart'; import 'package:twonly/src/services/signal/utils.signal.dart'; Future getProfileQrCodeData() async { @@ -80,21 +77,5 @@ Future addNewContactFromPublicProfile(PublicProfile profile) async { ), ); - if (added > 0) { - if (await createNewSignalSession(userdata)) { - // 1. Setup notifications keys with the other user - await setupNotificationWithUsers( - forceContact: userdata.userId.toInt(), - ); - // 2. Then send user request - await sendCipherText( - userdata.userId.toInt(), - EncryptedContent( - contactRequest: EncryptedContent_ContactRequest( - type: EncryptedContent_ContactRequest_Type.REQUEST, - ), - ), - ); - } - } + if (added > 0) await importSignalContactAndCreateRequest(userdata); } diff --git a/lib/src/views/chats/add_new_user.view.dart b/lib/src/views/chats/add_new_user.view.dart index aef43d6..a80ec70 100644 --- a/lib/src/views/chats/add_new_user.view.dart +++ b/lib/src/views/chats/add_new_user.view.dart @@ -9,8 +9,7 @@ import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; import 'package:twonly/src/services/api/messages.dart'; -import 'package:twonly/src/services/notifications/pushkeys.notifications.dart'; -import 'package:twonly/src/services/signal/session.signal.dart'; +import 'package:twonly/src/services/api/utils.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/components/alert_dialog.dart'; import 'package:twonly/src/views/components/avatar_icon.component.dart'; @@ -110,23 +109,7 @@ class _SearchUsernameView extends State { ), ); - if (added > 0) { - if (await createNewSignalSession(userdata)) { - // 1. Setup notifications keys with the other user - await setupNotificationWithUsers( - forceContact: userdata.userId.toInt(), - ); - // 2. Then send user request - await sendCipherText( - userdata.userId.toInt(), - EncryptedContent( - contactRequest: EncryptedContent_ContactRequest( - type: EncryptedContent_ContactRequest_Type.REQUEST, - ), - ), - ); - } - } + if (added > 0) await importSignalContactAndCreateRequest(userdata); } InputDecoration getInputDecoration(String hintText) { @@ -212,7 +195,7 @@ class ContactsListView extends StatelessWidget { child: IconButton( icon: const FaIcon(Icons.archive_outlined, size: 15), onPressed: () async { - const update = ContactsCompanion(requested: Value(false)); + const update = ContactsCompanion(deletedByUser: Value(true)); await twonlyDB.contactsDao.updateContact(contact.userId, update); }, ), diff --git a/lib/src/views/chats/chat_list_components/group_list_item.dart b/lib/src/views/chats/chat_list_components/group_list_item.dart index b760097..fa7a22f 100644 --- a/lib/src/views/chats/chat_list_components/group_list_item.dart +++ b/lib/src/views/chats/chat_list_components/group_list_item.dart @@ -129,10 +129,11 @@ class _UserListItem extends State { _previewMessages = [newLastMessage]; } - final msgs = - _previewMessages.where((x) => x.type == MessageType.media).toList(); + final msgs = _previewMessages + .where((x) => x.type == MessageType.media.name) + .toList(); if (msgs.isNotEmpty && - msgs.first.type == MessageType.media && + msgs.first.type == MessageType.media.name && !msgs.first.isDeletedFromSender && msgs.first.senderId != null && msgs.first.openedAt == null) { @@ -167,8 +168,9 @@ class _UserListItem extends State { } if (_hasNonOpenedMediaFile) { - final msgs = - _previewMessages.where((x) => x.type == MessageType.media).toList(); + final msgs = _previewMessages + .where((x) => x.type == MessageType.media.name) + .toList(); final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(msgs.first.mediaId!); if (mediaFile?.type != MediaType.audio) { diff --git a/lib/src/views/chats/chat_messages.view.dart b/lib/src/views/chats/chat_messages.view.dart index 771ac88..fab9358 100644 --- a/lib/src/views/chats/chat_messages.view.dart +++ b/lib/src/views/chats/chat_messages.view.dart @@ -200,7 +200,7 @@ class _ChatMessagesViewState extends State { } } index += 1; - if (msg.type == MessageType.text && + if (msg.type != MessageType.media.name && msg.senderId != null && msg.openedAt == null) { if (openedMessages[msg.senderId!] == null) { @@ -209,7 +209,7 @@ class _ChatMessagesViewState extends State { openedMessages[msg.senderId!]!.add(msg.messageId); } - if (msg.type == MessageType.media && msg.mediaStored) { + if (msg.type == MessageType.media.name && msg.mediaStored) { storedMediaFiles.add(msg); } diff --git a/lib/src/views/chats/chat_messages_components/bottom_sheets/share_additional.bottom_sheet.dart b/lib/src/views/chats/chat_messages_components/bottom_sheets/share_additional.bottom_sheet.dart new file mode 100644 index 0000000..508ce95 --- /dev/null +++ b/lib/src/views/chats/chat_messages_components/bottom_sheets/share_additional.bottom_sheet.dart @@ -0,0 +1,112 @@ +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:twonly/src/database/twonly.db.dart'; +import 'package:twonly/src/services/api/messages.dart'; +import 'package:twonly/src/utils/misc.dart'; +import 'package:twonly/src/views/shared/select_contacts.view.dart'; + +class ShareAdditionalView extends StatefulWidget { + const ShareAdditionalView({required this.group, super.key}); + + final Group group; + + @override + State createState() => _ShareAdditionalViewState(); +} + +class _ShareAdditionalViewState extends State { + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + Future openShareContactView() async { + final selectedContacts = await context.navPush( + SelectContactsView( + text: SelectedContactViewText( + title: context.lang.shareContactsTitle, + submitButton: (_, __) => context.lang.shareContactsSubmit, + submitIcon: FontAwesomeIcons.shareNodes, + ), + ), + ) as List?; + if (selectedContacts != null && selectedContacts.isNotEmpty) { + await insertAndSendContactShareMessage( + widget.group.groupId, + selectedContacts, + ); + } + if (mounted) { + Navigator.pop(context); + } + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Container( + padding: EdgeInsets.zero, + height: 220, + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(32), + topRight: Radius.circular(32), + ), + color: context.color.surface, + boxShadow: const [ + BoxShadow( + blurRadius: 10.9, + color: Color.fromRGBO(0, 0, 0, 0.1), + ), + ], + ), + child: Column( + children: [ + Container( + margin: const EdgeInsets.only(top: 30), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(32), + color: Colors.grey, + ), + height: 3, + width: 60, + ), + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + onTap: openShareContactView, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: context.color.surfaceContainer, + borderRadius: BorderRadius.circular(12), + ), + child: const FaIcon(FontAwesomeIcons.circleUser), + ), + const SizedBox(height: 8), + Text( + context.lang.shareContactsMenu, + textAlign: TextAlign.center, + ), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/src/views/chats/chat_messages_components/chat_list_entry.dart b/lib/src/views/chats/chat_messages_components/chat_list_entry.dart index 7b05a6d..e13aef5 100644 --- a/lib/src/views/chats/chat_messages_components/chat_list_entry.dart +++ b/lib/src/views/chats/chat_messages_components/chat_list_entry.dart @@ -12,8 +12,10 @@ import 'package:twonly/src/model/memory_item.model.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/views/chats/chat_messages_components/chat_reaction_row.dart'; import 'package:twonly/src/views/chats/chat_messages_components/entries/chat_audio_entry.dart'; +import 'package:twonly/src/views/chats/chat_messages_components/entries/chat_contacts.entry.dart'; import 'package:twonly/src/views/chats/chat_messages_components/entries/chat_media_entry.dart'; import 'package:twonly/src/views/chats/chat_messages_components/entries/chat_text_entry.dart'; +import 'package:twonly/src/views/chats/chat_messages_components/entries/chat_unkown.entry.dart'; import 'package:twonly/src/views/chats/chat_messages_components/entries/common.dart'; import 'package:twonly/src/views/chats/chat_messages_components/message_actions.dart'; import 'package:twonly/src/views/chats/chat_messages_components/message_context_menu.dart'; @@ -90,6 +92,49 @@ class _ChatListEntryState extends State { setState(() {}); } + Widget? _getChatEntry(BorderRadius borderRadius, int reactionsForWidth) { + if (widget.message.type == MessageType.text.name) { + return ChatTextEntry( + message: widget.message, + nextMessage: widget.nextMessage, + prevMessage: widget.prevMessage, + userIdToContact: widget.userIdToContact, + borderRadius: borderRadius, + minWidth: reactionsForWidth * 43, + ); + } + + if (widget.message.type == MessageType.media.name) { + if (mediaService == null) return null; + if (mediaService!.mediaFile.type == MediaType.audio) { + return ChatAudioEntry( + message: widget.message, + nextMessage: widget.nextMessage, + prevMessage: widget.prevMessage, + mediaService: mediaService!, + userIdToContact: widget.userIdToContact, + borderRadius: borderRadius, + minWidth: reactionsForWidth * 43, + ); + } + return ChatMediaEntry( + message: widget.message, + group: widget.group, + mediaService: mediaService!, + galleryItems: widget.galleryItems, + minWidth: reactionsForWidth * 43, + ); + } + + if (widget.message.type == MessageType.contacts.name) { + return ChatContactsEntry( + message: widget.message, + ); + } + + return const ChatUnknownEntry(); + } + @override Widget build(BuildContext context) { final right = widget.message.senderId == null; @@ -129,34 +174,7 @@ class _ChatListEntryState extends State { mediaService: mediaService, borderRadius: borderRadius, scrollToMessage: widget.scrollToMessage, - child: (widget.message.type == MessageType.text) - ? ChatTextEntry( - message: widget.message, - nextMessage: widget.nextMessage, - prevMessage: widget.prevMessage, - userIdToContact: widget.userIdToContact, - borderRadius: borderRadius, - minWidth: reactionsForWidth * 43, - ) - : (mediaService == null) - ? null - : (mediaService!.mediaFile.type == MediaType.audio) - ? ChatAudioEntry( - message: widget.message, - nextMessage: widget.nextMessage, - prevMessage: widget.prevMessage, - mediaService: mediaService!, - userIdToContact: widget.userIdToContact, - borderRadius: borderRadius, - minWidth: reactionsForWidth * 43, - ) - : ChatMediaEntry( - message: widget.message, - group: widget.group, - mediaService: mediaService!, - galleryItems: widget.galleryItems, - minWidth: reactionsForWidth * 43, - ), + child: _getChatEntry(borderRadius, reactionsForWidth), ), if (reactionsForWidth > 0) const SizedBox(height: 20, width: 10), ], diff --git a/lib/src/views/chats/chat_messages_components/entries/chat_contacts.entry.dart b/lib/src/views/chats/chat_messages_components/entries/chat_contacts.entry.dart new file mode 100644 index 0000000..f025012 --- /dev/null +++ b/lib/src/views/chats/chat_messages_components/entries/chat_contacts.entry.dart @@ -0,0 +1,201 @@ +import 'dart:convert'; + +import 'package:drift/drift.dart' show Value; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:twonly/globals.dart'; +import 'package:twonly/src/database/twonly.db.dart'; +import 'package:twonly/src/model/protobuf/client/generated/data.pb.dart'; +import 'package:twonly/src/services/api/utils.dart'; +import 'package:twonly/src/utils/log.dart'; +import 'package:twonly/src/views/chats/chat_messages_components/entries/common.dart'; +import 'package:twonly/src/views/components/better_text.dart'; + +class ChatContactsEntry extends StatefulWidget { + const ChatContactsEntry({ + required this.message, + super.key, + }); + + final Message message; + + @override + State createState() => _ChatContactsEntryState(); +} + +class _ChatContactsEntryState extends State { + @override + Widget build(BuildContext context) { + AdditionalMessageData? data; + + if (widget.message.additionalMessageData != null) { + try { + data = AdditionalMessageData.fromBuffer( + widget.message.additionalMessageData!, + ); + } catch (e) { + data = null; + } + } + + if (data == null || data.contacts.isEmpty) { + return const SizedBox.shrink(); + } + + final info = getBubbleInfo( + context, + widget.message, + null, + null, + null, + 0, + ); + + return Container( + constraints: BoxConstraints( + maxWidth: MediaQuery.of(context).size.width * 0.8, + ), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), + decoration: BoxDecoration( + color: info.color, + borderRadius: BorderRadius.circular(12), + ), + child: IntrinsicWidth( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + for (var i = 0; i < data.contacts.length; i++) ...[ + if (i > 0) + Divider( + height: 1, + color: Colors.white.withValues(alpha: 0.2), + ), + _ContactRow( + contact: data.contacts[i], + message: widget.message, + ), + ], + ], + ), + ), + ); + } +} + +class _ContactRow extends StatefulWidget { + const _ContactRow({ + required this.contact, + required this.message, + }); + + final SharedContact contact; + final Message message; + + @override + State<_ContactRow> createState() => _ContactRowState(); +} + +class _ContactRowState extends State<_ContactRow> { + bool _isLoading = false; + + Future _onContactClick() async { + setState(() { + _isLoading = true; + }); + + try { + final userdata = + await apiService.getUserById(widget.contact.userId.toInt()); + if (userdata == null) return; + + var verified = false; + if (userdata.publicIdentityKey == widget.contact.publicIdentityKey) { + final sender = + await twonlyDB.contactsDao.getContactById(widget.message.senderId!); + // in case the sender is verified and the public keys are the same, this trust can be transferred + verified = sender != null && sender.verified; + } + + final added = await twonlyDB.contactsDao.insertOnConflictUpdate( + ContactsCompanion( + username: Value(utf8.decode(userdata.username)), + userId: Value(userdata.userId.toInt()), + requested: const Value(false), + blocked: const Value(false), + deletedByUser: const Value(false), + verified: Value( + verified, + ), + ), + ); + + if (added > 0) await importSignalContactAndCreateRequest(userdata); + } catch (e) { + Log.error(e); + } finally { + if (mounted) { + setState(() { + _isLoading = false; + }); + } + } + } + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: twonlyDB.contactsDao.watchContact(widget.contact.userId.toInt()), + builder: (context, snapshot) { + final contactInDb = snapshot.data; + final isAdded = contactInDb != null || + widget.contact.userId.toInt() == gUser.userId; + + return GestureDetector( + onTap: (widget.message.senderId == null || isAdded || _isLoading) + ? null + : _onContactClick, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const FaIcon( + FontAwesomeIcons.user, + color: Colors.white, + size: 16, + ), + const SizedBox(width: 8), + Flexible( + child: BetterText( + text: widget.contact.displayName, + textColor: Colors.white, + ), + ), + if (widget.message.senderId != null && !isAdded) ...[ + const Spacer(), + const SizedBox(width: 8), + if (_isLoading) + const SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(Colors.white), + ), + ) + else + const FaIcon( + FontAwesomeIcons.userPlus, + color: Colors.white, + size: 16, + ), + ], + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/src/views/chats/chat_messages_components/entries/chat_media_entry.dart b/lib/src/views/chats/chat_messages_components/entries/chat_media_entry.dart index 1f89085..4a497b3 100644 --- a/lib/src/views/chats/chat_messages_components/entries/chat_media_entry.dart +++ b/lib/src/views/chats/chat_messages_components/entries/chat_media_entry.dart @@ -117,7 +117,7 @@ class _ChatMediaEntryState extends State { return GestureDetector( key: reopenMediaFile, onDoubleTap: onDoubleTap, - onTap: (widget.message.type == MessageType.media) ? onTap : null, + onTap: (widget.message.type == MessageType.media.name) ? onTap : null, child: SizedBox( width: (widget.minWidth > 150) ? widget.minWidth : 150, height: (widget.message.mediaStored && diff --git a/lib/src/views/chats/chat_messages_components/entries/chat_unkown.entry.dart b/lib/src/views/chats/chat_messages_components/entries/chat_unkown.entry.dart new file mode 100644 index 0000000..6c221e6 --- /dev/null +++ b/lib/src/views/chats/chat_messages_components/entries/chat_unkown.entry.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:twonly/src/utils/misc.dart'; +import 'package:twonly/src/views/components/better_text.dart'; + +class ChatUnknownEntry extends StatelessWidget { + const ChatUnknownEntry({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Container( + constraints: BoxConstraints( + maxWidth: MediaQuery.of(context).size.width * 0.8, + ), + padding: const EdgeInsets.only(left: 10, top: 6, bottom: 6, right: 10), + decoration: BoxDecoration( + color: isDarkMode(context) ? Colors.black : Colors.grey, + borderRadius: BorderRadius.circular(12), + ), + child: BetterText( + text: context.lang.updateTwonlyMessage, + textColor: isDarkMode(context) + ? const Color.fromARGB(255, 99, 99, 99) + : Colors.black, + ), + ); + } +} diff --git a/lib/src/views/chats/chat_messages_components/entries/common.dart b/lib/src/views/chats/chat_messages_components/entries/common.dart index da56997..f6be8f8 100644 --- a/lib/src/views/chats/chat_messages_components/entries/common.dart +++ b/lib/src/views/chats/chat_messages_components/entries/common.dart @@ -85,8 +85,8 @@ double measureTextWidth( bool combineTextMessageWithNext(Message message, Message? nextMessage) { if (nextMessage != null && nextMessage.content != null) { if (nextMessage.senderId == message.senderId) { - if (nextMessage.type == MessageType.text && - message.type == MessageType.text) { + if (nextMessage.type == MessageType.text.name && + message.type == MessageType.text.name) { if (!EmojiAnimation.supported(nextMessage.content!)) { final diff = nextMessage.createdAt.difference(message.createdAt).inMinutes; diff --git a/lib/src/views/chats/chat_messages_components/message_context_menu.dart b/lib/src/views/chats/chat_messages_components/message_context_menu.dart index 078e657..e2b12b1 100644 --- a/lib/src/views/chats/chat_messages_components/message_context_menu.dart +++ b/lib/src/views/chats/chat_messages_components/message_context_menu.dart @@ -127,7 +127,7 @@ class MessageContextMenu extends StatelessWidget { ), if (!message.isDeletedFromSender && message.senderId == null && - message.type == MessageType.text) + message.type == MessageType.text.name) ContextMenuItem( title: context.lang.edit, onTap: () async { diff --git a/lib/src/views/chats/chat_messages_components/message_input.dart b/lib/src/views/chats/chat_messages_components/message_input.dart index 6c195c7..af683a2 100644 --- a/lib/src/views/chats/chat_messages_components/message_input.dart +++ b/lib/src/views/chats/chat_messages_components/message_input.dart @@ -15,6 +15,7 @@ import 'package:twonly/src/services/api/mediafiles/upload.service.dart'; import 'package:twonly/src/services/api/messages.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/camera/camera_send_to.view.dart'; +import 'package:twonly/src/views/chats/chat_messages_components/bottom_sheets/share_additional.bottom_sheet.dart'; import 'package:twonly/src/views/chats/chat_messages_components/entries/chat_audio_entry.dart'; class MessageInput extends StatefulWidget { @@ -167,6 +168,19 @@ class _MessageInputState extends State { } } + Future _showAdditionalShareModal(BuildContext context) async { + // ignore: inference_failure_on_function_invocation + await showModalBottomSheet( + context: context, + backgroundColor: Colors.black, + builder: (context) { + return ShareAdditionalView( + group: widget.group, + ); + }, + ); + } + @override Widget build(BuildContext context) { return Column( @@ -312,6 +326,20 @@ class _MessageInputState extends State { ], ), ), + if (_textFieldController.text == '') + IconButton( + icon: const FaIcon(FontAwesomeIcons.camera), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return CameraSendToView(widget.group); + }, + ), + ); + }, + ), if (_textFieldController.text == '') GestureDetector( onLongPressMoveUpdate: (details) { @@ -452,18 +480,9 @@ class _MessageInputState extends State { ) else IconButton( - icon: const FaIcon(FontAwesomeIcons.camera), + icon: const FaIcon(FontAwesomeIcons.plus), padding: const EdgeInsets.all(15), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return CameraSendToView(widget.group); - }, - ), - ); - }, + onPressed: () => _showAdditionalShareModal(context), ), ], ), diff --git a/lib/src/views/chats/chat_messages_components/message_send_state_icon.dart b/lib/src/views/chats/chat_messages_components/message_send_state_icon.dart index c8400b2..2b40303 100644 --- a/lib/src/views/chats/chat_messages_components/message_send_state_icon.dart +++ b/lib/src/views/chats/chat_messages_components/message_send_state_icon.dart @@ -87,7 +87,7 @@ class _MessageSendStateIconState extends State { var text = ''; Widget? textWidget; textWidget = null; - final kindsAlreadyShown = HashSet(); + final kindsAlreadyShown = HashSet(); var hasLoader = false; GestureTapCallback? onTap; @@ -133,7 +133,7 @@ class _MessageSendStateIconState extends State { case MessageSendState.received: icon = Icon(Icons.square_rounded, size: 14, color: color); text = context.lang.messageSendState_Received; - if (message.type == MessageType.media && mediaFile != null) { + if (message.type == MessageType.media.name && mediaFile != null) { if (mediaFile.downloadState == DownloadState.pending) { text = context.lang.messageSendState_TapToLoad; } @@ -210,7 +210,7 @@ class _MessageSendStateIconState extends State { break; } - if (message.type == MessageType.media) { + if (message.type == MessageType.media.name) { icons.insert(0, icon); } else { icons.add(icon); diff --git a/lib/src/views/chats/chat_messages_components/response_container.dart b/lib/src/views/chats/chat_messages_components/response_container.dart index 1c27f90..f312de1 100644 --- a/lib/src/views/chats/chat_messages_components/response_container.dart +++ b/lib/src/views/chats/chat_messages_components/response_container.dart @@ -169,12 +169,12 @@ class _ResponsePreviewState extends State { var color = const Color.fromARGB(233, 68, 137, 255); if (_message != null) { - if (_message!.type == MessageType.text) { + if (_message!.type == MessageType.text.name) { if (_message!.content != null) { subtitle = truncateString(_message!.content!); } } - if (_message!.type == MessageType.media && _mediaService != null) { + if (_message!.type == MessageType.media.name && _mediaService != null) { switch (_mediaService!.mediaFile.type) { case MediaType.image: subtitle = context.lang.image; diff --git a/lib/src/views/chats/media_viewer_components/additional_message_content.dart b/lib/src/views/chats/media_viewer_components/additional_message_content.dart index 7f810c2..5371b7f 100644 --- a/lib/src/views/chats/media_viewer_components/additional_message_content.dart +++ b/lib/src/views/chats/media_viewer_components/additional_message_content.dart @@ -45,6 +45,7 @@ class AdditionalMessageContent extends StatelessWidget { ], ), ); + // ignore: no_default_cases default: } // ignore: empty_catches diff --git a/lib/src/views/contact/contact.view.dart b/lib/src/views/contact/contact.view.dart index 1deebbc..41bc54b 100644 --- a/lib/src/views/contact/contact.view.dart +++ b/lib/src/views/contact/contact.view.dart @@ -2,7 +2,9 @@ import 'dart:async'; import 'package:drift/drift.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:go_router/go_router.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/utils/misc.dart'; @@ -14,7 +16,6 @@ import 'package:twonly/src/views/components/max_flame_list_title.dart'; import 'package:twonly/src/views/components/select_chat_deletion_time.comp.dart'; import 'package:twonly/src/views/components/verified_shield.dart'; import 'package:twonly/src/views/groups/group.view.dart'; -import 'package:twonly/src/views/public_profile.view.dart'; class ContactView extends StatefulWidget { const ContactView(this.userId, {super.key}); @@ -187,14 +188,7 @@ class _ContactViewState extends State { icon: FontAwesomeIcons.shieldHeart, text: context.lang.contactVerifyNumberTitle, onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const PublicProfileView(); - }, - ), - ); + await context.push(Routes.settingsPublicProfile); setState(() {}); }, ), diff --git a/lib/src/views/shared/select_contacts.view.dart b/lib/src/views/shared/select_contacts.view.dart new file mode 100644 index 0000000..6b5a989 --- /dev/null +++ b/lib/src/views/shared/select_contacts.view.dart @@ -0,0 +1,271 @@ +import 'dart:async'; +import 'dart:collection'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:twonly/globals.dart'; +import 'package:twonly/src/database/daos/contacts.dao.dart'; +import 'package:twonly/src/database/twonly.db.dart'; +import 'package:twonly/src/utils/misc.dart'; +import 'package:twonly/src/views/components/avatar_icon.component.dart'; +import 'package:twonly/src/views/components/flame.dart'; +import 'package:twonly/src/views/components/user_context_menu.component.dart'; + +class SelectedContactViewText { + const SelectedContactViewText({ + required this.title, + required this.submitButton, + required this.submitIcon, + }); + final String title; + final String Function(int selected, int? limit) submitButton; + final IconData submitIcon; +} + +class SelectContactsView extends StatefulWidget { + const SelectContactsView({ + required this.text, + this.alreadySelected, + this.limit, + super.key, + }); + final SelectedContactViewText text; + final List? alreadySelected; + final int? limit; + @override + State createState() => _SelectAdditionalUsers(); +} + +class _SelectAdditionalUsers extends State { + List contacts = []; + List allContacts = []; + final TextEditingController searchUserName = TextEditingController(); + late StreamSubscription> contactSub; + + final HashSet selectedUsers = HashSet(); + late HashSet _alreadySelected; + + @override + void initState() { + super.initState(); + + _alreadySelected = HashSet.from(widget.alreadySelected ?? []); + + final stream = twonlyDB.contactsDao.watchAllAcceptedContacts(); + + contactSub = stream.listen((update) async { + update.sort( + (a, b) => getContactDisplayName(a).compareTo(getContactDisplayName(b)), + ); + setState(() { + allContacts = update; + }); + await filterUsers(); + }); + } + + @override + void dispose() { + unawaited(contactSub.cancel()); + super.dispose(); + } + + Future filterUsers() async { + if (searchUserName.value.text.isEmpty) { + setState(() { + contacts = allContacts; + }); + return; + } + final usersFiltered = allContacts + .where( + (user) => getContactDisplayName(user) + .toLowerCase() + .contains(searchUserName.value.text.toLowerCase()), + ) + .toList(); + setState(() { + contacts = usersFiltered; + }); + } + + void toggleSelectedUser(int userId) { + if (_alreadySelected.contains(userId)) return; + if (!selectedUsers.contains(userId)) { + if (widget.limit == null || selectedUsers.length < widget.limit!) { + selectedUsers.add(userId); + } + } else { + selectedUsers.remove(userId); + } + setState(() {}); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => FocusScope.of(context).unfocus(), + child: Scaffold( + appBar: AppBar( + title: Text(widget.text.title), + ), + floatingActionButton: FilledButton.icon( + onPressed: selectedUsers.isEmpty + ? null + : () => Navigator.pop(context, selectedUsers.toList()), + label: Text( + widget.text.submitButton( + selectedUsers.length + (widget.alreadySelected?.length ?? 0), + widget.limit, + ), + ), + icon: FaIcon(widget.text.submitIcon), + ), + body: SafeArea( + child: Padding( + padding: + const EdgeInsets.only(bottom: 40, left: 10, top: 20, right: 10), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: TextField( + onChanged: (_) async { + await filterUsers(); + }, + controller: searchUserName, + decoration: getInputDecoration( + context, + context.lang.shareImageSearchAllContacts, + ), + ), + ), + const SizedBox(height: 10), + Expanded( + child: ListView.builder( + restorationId: 'new_message_users_list', + itemCount: + contacts.length + (selectedUsers.isEmpty ? 0 : 2), + itemBuilder: (context, i) { + if (selectedUsers.isNotEmpty) { + final selected = selectedUsers.toList(); + if (i == 0) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 18), + constraints: const BoxConstraints( + maxHeight: 150, + ), + child: SingleChildScrollView( + child: LayoutBuilder( + builder: (context, constraints) { + return Wrap( + spacing: 8, + children: selected.map((w) { + return _Chip( + contact: allContacts + .firstWhere((t) => t.userId == w), + onTap: toggleSelectedUser, + ); + }).toList(), + ); + }, + ), + ), + ); + } + if (i == 1) { + return const Divider(); + } + i -= 2; + } + final user = contacts[i]; + return UserContextMenu( + key: ValueKey(user.userId), + contact: user, + child: ListTile( + title: Row( + children: [ + Text(getContactDisplayName(user)), + FlameCounterWidget( + contactId: user.userId, + prefix: true, + ), + ], + ), + subtitle: (_alreadySelected.contains(user.userId)) + ? Text(context.lang.alreadyInGroup) + : null, + leading: AvatarIcon( + contactId: user.userId, + fontSize: 13, + ), + trailing: Checkbox( + value: selectedUsers.contains(user.userId) | + _alreadySelected.contains(user.userId), + side: WidgetStateBorderSide.resolveWith( + (states) { + if (states.contains(WidgetState.selected)) { + return const BorderSide(width: 0); + } + return BorderSide( + color: Theme.of(context).colorScheme.outline, + ); + }, + ), + onChanged: (value) { + toggleSelectedUser(user.userId); + }, + ), + onTap: () { + toggleSelectedUser(user.userId); + }, + ), + ); + }, + ), + ), + ], + ), + ), + ), + ), + ); + } +} + +class _Chip extends StatelessWidget { + const _Chip({ + required this.contact, + required this.onTap, + }); + final Contact contact; + final void Function(int) onTap; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => onTap(contact.userId), + child: Chip( + avatar: AvatarIcon( + contactId: contact.userId, + fontSize: 10, + ), + label: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + getContactDisplayName(contact), + style: const TextStyle(fontSize: 14), + overflow: TextOverflow.ellipsis, + ), + const SizedBox(width: 15), + const FaIcon( + FontAwesomeIcons.xmark, + color: Colors.grey, + size: 12, + ), + ], + ), + ), + ); + } +} From 2bf0249f7293e2bc17142df5ef5575b39720244f Mon Sep 17 00:00:00 2001 From: otsmr Date: Fri, 13 Feb 2026 02:16:13 +0100 Subject: [PATCH 8/8] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78629bc..2152468 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 0.0.92 +- Adds the option to share contacts - Adds option to zoom in received images / videos - Fixes issue with "reuploaded requested" not working - Fixes race condition while writing to the log file