diff --git a/CHANGELOG.md b/CHANGELOG.md index d84b081..0655968 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 0.1.2 - New: Developer settings to reduce flames +- New: Clicking on “Text Notifications” will now open the chat directly (Android only) - Improve: Improved troubleshooting for issues with push notifications - Fix: Flash not activated when starting a video recording - Fix: Problem sending media when a recipient has deleted their account. diff --git a/ios/Podfile.lock b/ios/Podfile.lock index e3d6f23..d8c25ba 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -56,13 +56,20 @@ PODS: - FirebaseAnalytics (~> 12.9.0) - Firebase/CoreOnly (12.9.0): - FirebaseCore (~> 12.9.0) + - Firebase/Installations (12.9.0): + - Firebase/CoreOnly + - FirebaseInstallations (~> 12.9.0) - Firebase/Messaging (12.9.0): - Firebase/CoreOnly - FirebaseMessaging (~> 12.9.0) - - firebase_core (4.5.0): + - firebase_app_installations (0.4.1): + - Firebase/Installations (= 12.9.0) + - firebase_core + - Flutter + - firebase_core (4.6.0): - Firebase/CoreOnly (= 12.9.0) - Flutter - - firebase_messaging (16.1.2): + - firebase_messaging (16.1.3): - Firebase/Messaging (= 12.9.0) - firebase_core - Flutter @@ -278,17 +285,17 @@ PODS: - PromisesObjC (2.4.0) - restart_app (1.7.3): - Flutter - - SDWebImage (5.21.6): - - SDWebImage/Core (= 5.21.6) - - SDWebImage/Core (5.21.6) + - SDWebImage (5.21.7): + - SDWebImage/Core (= 5.21.7) + - SDWebImage/Core (5.21.7) - SDWebImageWebPCoder (0.15.0): - libwebp (~> 1.0) - SDWebImage/Core (~> 5.17) - - Sentry/HybridSDK (8.56.2) - - sentry_flutter (9.14.0): + - Sentry/HybridSDK (8.58.0) + - sentry_flutter (9.16.0): - Flutter - FlutterMacOS - - Sentry/HybridSDK (= 8.56.2) + - Sentry/HybridSDK (= 8.58.0) - share_plus (0.0.1): - Flutter - shared_preferences_foundation (0.0.1): @@ -297,32 +304,32 @@ PODS: - sqflite_darwin (0.0.4): - Flutter - FlutterMacOS - - sqlite3 (3.51.1): - - sqlite3/common (= 3.51.1) - - sqlite3/common (3.51.1) - - sqlite3/dbstatvtab (3.51.1): + - sqlite3 (3.52.0): + - sqlite3/common (= 3.52.0) + - sqlite3/common (3.52.0) + - sqlite3/dbstatvtab (3.52.0): - sqlite3/common - - sqlite3/fts5 (3.51.1): + - sqlite3/fts5 (3.52.0): - sqlite3/common - - sqlite3/math (3.51.1): + - sqlite3/math (3.52.0): - sqlite3/common - - sqlite3/perf-threadsafe (3.51.1): + - sqlite3/perf-threadsafe (3.52.0): - sqlite3/common - - sqlite3/rtree (3.51.1): + - sqlite3/rtree (3.52.0): - sqlite3/common - - sqlite3/session (3.51.1): + - sqlite3/session (3.52.0): - sqlite3/common - sqlite3_flutter_libs (0.0.1): - Flutter - FlutterMacOS - - sqlite3 (~> 3.51.1) + - sqlite3 (~> 3.52.0) - sqlite3/dbstatvtab - sqlite3/fts5 - sqlite3/math - sqlite3/perf-threadsafe - sqlite3/rtree - sqlite3/session - - SwiftProtobuf (1.34.1) + - SwiftProtobuf (1.36.1) - SwiftyGif (5.4.5) - url_launcher_ios (0.0.1): - Flutter @@ -343,6 +350,7 @@ DEPENDENCIES: - emoji_picker_flutter (from `.symlinks/plugins/emoji_picker_flutter/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`) - Firebase + - firebase_app_installations (from `.symlinks/plugins/firebase_app_installations/ios`) - firebase_core (from `.symlinks/plugins/firebase_core/ios`) - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) - FirebaseCore @@ -430,6 +438,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/emoji_picker_flutter/ios" file_picker: :path: ".symlinks/plugins/file_picker/ios" + firebase_app_installations: + :path: ".symlinks/plugins/firebase_app_installations/ios" firebase_core: :path: ".symlinks/plugins/firebase_core/ios" firebase_messaging: @@ -493,7 +503,7 @@ SPEC CHECKSUMS: app_links: a754cbec3c255bd4bbb4d236ecc06f28cd9a7ce8 audio_waveforms: a6dde7fe7c0ea05f06ffbdb0f7c1b2b2ba6cedcf background_downloader: 50e91d979067b82081aba359d7d916b3ba5fadad - camera_avfoundation: 5675ca25298b6f81fa0a325188e7df62cc217741 + camera_avfoundation: 968a9a5323c79a99c166ad9d7866bfd2047b5a9b connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd cryptography_flutter_plus: 44f4e9e4079395fcbb3e7809c0ac2c6ae2d9576f device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe @@ -502,8 +512,9 @@ SPEC CHECKSUMS: emoji_picker_flutter: ece213fc274bdddefb77d502d33080dc54e616cc file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be Firebase: 065f2bb395062046623036d8e6dc857bc2521d56 - firebase_core: afac1aac13c931e0401c7e74ed1276112030efab - firebase_messaging: 7cb2727feb789751fc6936bcc8e08408970e2820 + firebase_app_installations: 1abd8d071ea2022d7888f7a9713710c37136ff91 + firebase_core: 8e6f58412ca227827c366b92e7cee047a2148c60 + firebase_messaging: c3aa897e0d40109cfb7927c40dc0dea799863f3b FirebaseAnalytics: cd7d01d352f3c237c9a0e31552c257cd0b0c0352 FirebaseCore: 428912f751178b06bef0a1793effeb4a5e09a9b8 FirebaseCoreInternal: b321eafae5362113bc182956fafc9922cfc77b72 @@ -544,16 +555,16 @@ SPEC CHECKSUMS: pro_video_editor: 44ef9a6d48dbd757ed428cf35396dd05f35c7830 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 restart_app: 0714144901e260eae68f7afc2fc4aacc1a323ad2 - SDWebImage: 1bb6a1b84b6fe87b972a102bdc77dd589df33477 + SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf SDWebImageWebPCoder: 0e06e365080397465cc73a7a9b472d8a3bd0f377 - Sentry: b53951377b78e21a734f5dc8318e333dbfc682d7 - sentry_flutter: 841fa2fe08dc72eb95e2320b76e3f751f3400cf5 + Sentry: d587a8fe91ca13503ecd69a1905f3e8a0fcf61be + sentry_flutter: 31101687061fb85211ebab09ce6eb8db4e9ba74f share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 - sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b - sqlite3_flutter_libs: d13b8b3003f18f596e542bcb9482d105577eff41 - SwiftProtobuf: c901f00a3e125dc33cac9b16824da85682ee47da + sqlite3: a51c07cf16e023d6c48abd5e5791a61a47354921 + sqlite3_flutter_libs: b3e120efe9a82017e5552a620f696589ed4f62ab + SwiftProtobuf: 9e106a71456f4d3f6a3b0c8fd87ef0be085efc38 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a diff --git a/lib/src/services/api.service.dart b/lib/src/services/api.service.dart index f54ace1..4b65f32 100644 --- a/lib/src/services/api.service.dart +++ b/lib/src/services/api.service.dart @@ -133,9 +133,7 @@ class ApiService { return; } reconnectionTimer?.cancel(); - Log.info('Starting reconnection timer with $_reconnectionDelay s delay'); reconnectionTimer = Timer(Duration(seconds: _reconnectionDelay), () async { - Log.info('Reconnection timer triggered'); reconnectionTimer = null; // only try to reconnect in case the app is in the foreground if (!globalIsAppInBackground) { diff --git a/lib/src/services/api/messages.dart b/lib/src/services/api/messages.dart index 857b0d1..7addc79 100644 --- a/lib/src/services/api/messages.dart +++ b/lib/src/services/api/messages.dart @@ -111,8 +111,8 @@ Future<(Uint8List, Uint8List?)?> tryToSendCompleteMessage({ ); Uint8List? pushData; - if (pushNotification != null && receipt.retryCount <= 3) { - /// In case the message has to be resend more than three times, do not show a notification again... + if (pushNotification != null && receipt.retryCount <= 1) { + // Only show the push notification the first two time. pushData = await encryptPushNotification( receipt.contactId, pushNotification, diff --git a/lib/src/services/api/server_messages.dart b/lib/src/services/api/server_messages.dart index 75c7f7a..6502ba4 100644 --- a/lib/src/services/api/server_messages.dart +++ b/lib/src/services/api/server_messages.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:clock/clock.dart'; import 'package:drift/drift.dart'; import 'package:hashlib/random.dart'; @@ -25,6 +26,7 @@ import 'package:twonly/src/services/api/client2client/reaction.c2c.dart'; import 'package:twonly/src/services/api/client2client/text_message.c2c.dart'; import 'package:twonly/src/services/api/messages.dart'; import 'package:twonly/src/services/group.services.dart'; +import 'package:twonly/src/services/notifications/background.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'; @@ -164,7 +166,7 @@ Future handleClient2ClientMessage(NewMessage newMessage) async { final ( encryptedContent, plainTextContent, - ) = await handleEncryptedMessage( + ) = await handleEncryptedMessageRaw( fromUserId, encryptedContentRaw, message.type, @@ -182,6 +184,9 @@ Future handleClient2ClientMessage(NewMessage newMessage) async { encryptedContent: encryptedContent.writeToBuffer(), ); receiptIdDB = const Value.absent(); + } else { + // Message was successful processed + // } } @@ -206,19 +211,19 @@ Future handleClient2ClientMessage(NewMessage newMessage) async { } } -Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage( +Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessageRaw( int fromUserId, Uint8List encryptedContentRaw, Message_Type messageType, String receiptId, ) async { - final (content, decryptionErrorType) = await signalDecryptMessage( + final (encryptedContent, decryptionErrorType) = await signalDecryptMessage( fromUserId, encryptedContentRaw, messageType.value, ); - if (content == null) { + if (encryptedContent == null) { return ( null, PlaintextContent() @@ -227,6 +232,27 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage( ); } + final (a, b) = await handleEncryptedMessage( + fromUserId, + encryptedContent, + messageType, + receiptId, + ); + + if (Platform.isAndroid && a == null && b == null) { + // Message was handled without any error -> Show push notification to the user. + await showPushNotificationFromServerMessages(fromUserId, encryptedContent); + } + + return (a, b); +} + +Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage( + int fromUserId, + EncryptedContent content, + Message_Type messageType, + String receiptId, +) async { // We got a valid message fromUserId, so mark all messages which where // send to the user but not yet ACK for retransmission. All marked messages // will be either transmitted again after a new server connection (minimum 20 seconds). diff --git a/lib/src/services/flame.service.dart b/lib/src/services/flame.service.dart index 715fe25..f25a5df 100644 --- a/lib/src/services/flame.service.dart +++ b/lib/src/services/flame.service.dart @@ -176,6 +176,6 @@ bool isItPossibleToRestoreFlames(Group group) { return group.maxFlameCounter > 2 && flameCounter < group.maxFlameCounter && group.maxFlameCounterFrom!.isAfter( - clock.now().subtract(const Duration(days: 5)), + clock.now().subtract(const Duration(days: 7)), ); } diff --git a/lib/src/services/mediafiles/compression.service.dart b/lib/src/services/mediafiles/compression.service.dart index 7aee32c..f79a4e1 100644 --- a/lib/src/services/mediafiles/compression.service.dart +++ b/lib/src/services/mediafiles/compression.service.dart @@ -72,13 +72,19 @@ Future compressAndOverlayVideo(MediaFileService media) async { try { final task = VideoRenderData( - video: EditorVideo.file(media.originalPath), - imageBytes: media.overlayImagePath.readAsBytesSync(), + videoSegments: [ + VideoSegment(video: EditorVideo.file(media.originalPath)), + ], + imageLayers: [ + ImageLayer(image: EditorLayerImage.file(media.overlayImagePath)), + ], enableAudio: !media.removeAudio, ); - await ProVideoEditor.instance - .renderVideoToFile(media.ffmpegOutputPath.path, task); + await ProVideoEditor.instance.renderVideoToFile( + media.ffmpegOutputPath.path, + task, + ); if (Platform.isIOS || media.ffmpegOutputPath.statSync().size >= 10_000_000 || @@ -115,8 +121,8 @@ Future compressAndOverlayVideo(MediaFileService media) async { final sizeFrom = (media.ffmpegOutputPath.statSync().size / 1024 / 1024) .toStringAsFixed(2); - final sizeTo = - (media.tempPath.statSync().size / 1024 / 1024).toStringAsFixed(2); + final sizeTo = (media.tempPath.statSync().size / 1024 / 1024) + .toStringAsFixed(2); Log.info( 'It took ${stopwatch.elapsedMilliseconds}ms to compress the video. Reduced from $sizeFrom to $sizeTo bytes.', diff --git a/lib/src/services/notifications/background.notifications.dart b/lib/src/services/notifications/background.notifications.dart index f7eaacc..c8989da 100644 --- a/lib/src/services/notifications/background.notifications.dart +++ b/lib/src/services/notifications/background.notifications.dart @@ -6,10 +6,12 @@ import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart'; import 'package:cryptography_plus/cryptography_plus.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/constants/secure_storage_keys.dart'; import 'package:twonly/src/localization/generated/app_localizations.dart'; import 'package:twonly/src/localization/generated/app_localizations_de.dart'; import 'package:twonly/src/localization/generated/app_localizations_en.dart'; +import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; 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/utils/log.dart'; @@ -45,10 +47,34 @@ Future customLocalPushNotification(String title, String msg) async { ); } +Future showPushNotificationFromServerMessages( + int fromUserId, + EncryptedContent encryptedContent, +) async { + final pushData = await getPushNotificationFromEncryptedContent( + null, // this is the toUserID which must be null as this means that the targetMessageId was send from this user. + null, + encryptedContent, + ); + if (pushData != null) { + final pushUsers = await getPushKeys(SecureStorageKeys.receivingPushKeys); + for (final pushUser in pushUsers) { + if (pushUser.userId.toInt() == fromUserId) { + String? groupId; + if (encryptedContent.hasGroupId()) { + groupId = encryptedContent.groupId; + } + return showLocalPushNotification(pushUser, pushData, groupId: groupId); + } + } + } +} + Future handlePushData(String pushDataB64) async { try { - final pushData = - EncryptedPushNotification.fromBuffer(base64.decode(pushDataB64)); + final pushData = EncryptedPushNotification.fromBuffer( + base64.decode(pushDataB64), + ); PushNotification? pushNotification; PushUser? foundPushUser; @@ -121,8 +147,10 @@ Future tryDecryptMessage( mac: Mac(push.mac), ); - final plaintext = - await chacha20.decrypt(secretBox, secretKey: secretKeyData); + final plaintext = await chacha20.decrypt( + secretBox, + secretKey: secretKeyData, + ); return PushNotification.fromBuffer(plaintext); } catch (e) { // this error is allowed to happen... @@ -132,8 +160,9 @@ Future tryDecryptMessage( Future showLocalPushNotification( PushUser pushUser, - PushNotification pushNotification, -) async { + PushNotification pushNotification, { + String? groupId, +}) async { String? title; String? body; @@ -174,13 +203,25 @@ Future showLocalPushNotification( iOS: darwinNotificationDetails, ); + String? payload; + + if (groupId != null && + (pushNotification.kind == PushKind.text || + pushNotification.kind == PushKind.response || + pushNotification.kind == PushKind.reactionToAudio || + pushNotification.kind == PushKind.reactionToImage || + pushNotification.kind == PushKind.reactionToText || + pushNotification.kind == PushKind.reactionToAudio)) { + payload = Routes.chatsMessages(groupId); + } + await flutterLocalNotificationsPlugin.show( - pushUser.userId.toInt() % - 2147483647, // Invalid argument (id): must fit within the size of a 32-bit integer + // Invalid argument (id): must fit within the size of a 32-bit integer + pushUser.userId.toInt() % 2147483647, title, body, notificationDetails, - // payload: pushNotification.kind.name, + payload: payload, ); } @@ -259,17 +300,22 @@ String getPushNotificationText(PushNotification pushNotification) { PushKind.storedMediaFile.name: lang.notificationStoredMediaFile, PushKind.reaction.name: lang.notificationReaction, PushKind.reopenedMedia.name: lang.notificationReopenedMedia, - PushKind.reactionToVideo.name: - lang.notificationReactionToVideo(pushNotification.additionalContent), - PushKind.reactionToAudio.name: - lang.notificationReactionToAudio(pushNotification.additionalContent), - PushKind.reactionToText.name: - lang.notificationReactionToText(pushNotification.additionalContent), - PushKind.reactionToImage.name: - lang.notificationReactionToImage(pushNotification.additionalContent), + PushKind.reactionToVideo.name: lang.notificationReactionToVideo( + pushNotification.additionalContent, + ), + PushKind.reactionToAudio.name: lang.notificationReactionToAudio( + pushNotification.additionalContent, + ), + PushKind.reactionToText.name: lang.notificationReactionToText( + pushNotification.additionalContent, + ), + PushKind.reactionToImage.name: lang.notificationReactionToImage( + pushNotification.additionalContent, + ), PushKind.response.name: lang.notificationResponse(inGroup), - PushKind.addedToGroup.name: - lang.notificationAddedToGroup(pushNotification.additionalContent), + PushKind.addedToGroup.name: lang.notificationAddedToGroup( + pushNotification.additionalContent, + ), }; return pushNotificationText[pushNotification.kind.name] ?? ''; diff --git a/lib/src/services/notifications/fcm.notifications.dart b/lib/src/services/notifications/fcm.notifications.dart index e7884f7..ce257a7 100644 --- a/lib/src/services/notifications/fcm.notifications.dart +++ b/lib/src/services/notifications/fcm.notifications.dart @@ -110,35 +110,23 @@ Future initFCMService() async { options: DefaultFirebaseOptions.currentPlatform, ); - unawaited(checkForTokenUpdates()); + await checkForTokenUpdates(); FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); - // You may set the permission requests to "provisional" which allows the user to choose what type - // of notifications they would like to receive once the user receives a notification. - // final notificationSettings = - // await FirebaseMessaging.instance.requestPermission(provisional: true); await FirebaseMessaging.instance.requestPermission(); - // For apple platforms, ensure the APNS token is available before making any FCM plugin API calls - // if (Platform.isIOS) { - // final apnsToken = await FirebaseMessaging.instance.getAPNSToken(); - // if (apnsToken == null) { - // return; - // } - // } - FirebaseMessaging.onMessage.listen(handleRemoteMessage); } @pragma('vm:entry-point') Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { - initLogger(); - // Log.info('Handling a background message: ${message.messageId}'); + final isInitialized = await initBackgroundExecution(); + Log.info('Handling a background message: ${message.messageId}'); await handleRemoteMessage(message); if (Platform.isAndroid) { - if (await initBackgroundExecution()) { + if (isInitialized) { await handlePeriodicTask(); } } else { @@ -164,7 +152,11 @@ Future handleRemoteMessage(RemoteMessage message) async { final body = message.notification?.body ?? message.data['body'] as String? ?? ''; await customLocalPushNotification(title, body); - } else if (message.data['push_data'] != null) { - await handlePushData(message.data['push_data'] as String); } + + // On Android the push notification is now shown in the server_message.dart. This ensures + // that the messages was successfully decrypted before showing the push notification + // else if (message.data['push_data'] != null) { + // await handlePushData(message.data['push_data'] as String); + // } } diff --git a/lib/src/services/notifications/pushkeys.notifications.dart b/lib/src/services/notifications/pushkeys.notifications.dart index 2df0f34..4d00549 100644 --- a/lib/src/services/notifications/pushkeys.notifications.dart +++ b/lib/src/services/notifications/pushkeys.notifications.dart @@ -49,13 +49,15 @@ Future setupNotificationWithUsers({ final contacts = await twonlyDB.contactsDao.getAllContacts(); for (final contact in contacts) { - final pushUser = - pushUsers.firstWhereOrNull((x) => x.userId == contact.userId); + final pushUser = pushUsers.firstWhereOrNull( + (x) => x.userId == contact.userId, + ); if (pushUser != null && pushUser.pushKeys.isNotEmpty) { // make it harder to predict the change of the key - final timeBefore = - clock.now().subtract(Duration(days: 10 + random.nextInt(5))); + final timeBefore = clock.now().subtract( + Duration(days: 10 + random.nextInt(5)), + ); final lastKey = pushUser.pushKeys.last; final createdAt = DateTime.fromMillisecondsSinceEpoch( lastKey.createdAtUnixTimestamp.toInt(), @@ -197,7 +199,7 @@ Future updateLastMessageId(int fromUserId, String messageId) async { } Future getPushNotificationFromEncryptedContent( - int toUserId, + int? toUserId, String? messageId, EncryptedContent content, ) async { @@ -210,7 +212,7 @@ Future getPushNotificationFromEncryptedContent( final msg = await twonlyDB.messagesDao .getMessageById(content.reaction.targetMessageId) .getSingleOrNull(); - if (msg == null || msg.senderId == null || msg.senderId != toUserId) { + if (msg == null || msg.senderId != toUserId) { return null; } if (msg.content != null) { @@ -285,7 +287,7 @@ Future getPushNotificationFromEncryptedContent( .getMessageById(content.reaction.targetMessageId) .getSingleOrNull(); // These notifications should only be send to the original sender. - if (msg == null || msg.senderId == null || msg.senderId != toUserId) { + if (msg == null || msg.senderId != toUserId) { return null; } switch (content.mediaUpdate.type) { diff --git a/lib/src/utils/log.dart b/lib/src/utils/log.dart index f40302e..29281db 100644 --- a/lib/src/utils/log.dart +++ b/lib/src/utils/log.dart @@ -8,8 +8,11 @@ import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/src/utils/exclusive_access.dart'; +bool _isInitialized = false; + void initLogger() { - // Logger.root.level = kReleaseMode ? Level.INFO : Level.ALL; + if (_isInitialized) return; + _isInitialized = true; Logger.root.level = Level.ALL; Logger.root.onRecord.listen((record) async { unawaited(_writeLogToFile(record)); diff --git a/lib/src/views/home.view.dart b/lib/src/views/home.view.dart index 1e24283..a4234a0 100644 --- a/lib/src/views/home.view.dart +++ b/lib/src/views/home.view.dart @@ -4,6 +4,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_sharing_intent/model/sharing_file.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; +import 'package:twonly/src/providers/routing.provider.dart'; import 'package:twonly/src/services/intent/links.intent.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/services/notifications/setup.notifications.dart'; @@ -106,7 +108,12 @@ class HomeViewState extends State { }); }; selectNotificationStream.stream.listen((response) async { - globalUpdateOfHomeViewPageIndex(0); + if (response.payload != null && + response.payload!.startsWith(Routes.chats)) { + await routerProvider.push(response.payload!); + } else { + globalUpdateOfHomeViewPageIndex(0); + } }); unawaited(_mainCameraController.selectCamera(0, true)); unawaited(initAsync()); @@ -140,13 +147,26 @@ class HomeViewState extends State { } Future initAsync() async { - final notificationAppLaunchDetails = - await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails(); + final notificationAppLaunchDetails = await flutterLocalNotificationsPlugin + .getNotificationAppLaunchDetails(); if (widget.initialPage == 0 || (notificationAppLaunchDetails != null && notificationAppLaunchDetails.didNotificationLaunchApp)) { - globalUpdateOfHomeViewPageIndex(0); + var pushed = false; + + if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) { + final payload = + notificationAppLaunchDetails?.notificationResponse?.payload; + if (payload != null && payload.startsWith(Routes.chats)) { + await routerProvider.push(payload); + pushed = true; + } + } + + if (!pushed) { + globalUpdateOfHomeViewPageIndex(0); + } } final draftMedia = await twonlyDB.mediaFilesDao.getDraftMediaFile(); @@ -168,8 +188,9 @@ class HomeViewState extends State { Widget build(BuildContext context) { return Scaffold( body: GestureDetector( - onDoubleTap: - offsetRatio == 0 ? _mainCameraController.onDoubleTap : null, + onDoubleTap: offsetRatio == 0 + ? _mainCameraController.onDoubleTap + : null, onTapDown: offsetRatio == 0 ? _mainCameraController.onTapDown : null, child: Stack( children: [