opening chat if clicked on the notification

This commit is contained in:
otsmr 2026-04-06 00:02:55 +02:00
parent aa26766bdf
commit 267e2bd376
12 changed files with 200 additions and 94 deletions

View file

@ -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.

View file

@ -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

View file

@ -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) {

View file

@ -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,

View file

@ -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<void> handleClient2ClientMessage(NewMessage newMessage) async {
final (
encryptedContent,
plainTextContent,
) = await handleEncryptedMessage(
) = await handleEncryptedMessageRaw(
fromUserId,
encryptedContentRaw,
message.type,
@ -182,6 +184,9 @@ Future<void> handleClient2ClientMessage(NewMessage newMessage) async {
encryptedContent: encryptedContent.writeToBuffer(),
);
receiptIdDB = const Value.absent();
} else {
// Message was successful processed
//
}
}
@ -206,19 +211,19 @@ Future<void> 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).

View file

@ -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)),
);
}

View file

@ -72,13 +72,19 @@ Future<void> 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<void> 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.',

View file

@ -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<void> customLocalPushNotification(String title, String msg) async {
);
}
Future<void> 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<void> 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<PushNotification?> 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<PushNotification?> tryDecryptMessage(
Future<void> showLocalPushNotification(
PushUser pushUser,
PushNotification pushNotification,
) async {
PushNotification pushNotification, {
String? groupId,
}) async {
String? title;
String? body;
@ -174,13 +203,25 @@ Future<void> 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] ?? '';

View file

@ -110,35 +110,23 @@ Future<void> 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<void> _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<void> 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);
// }
}

View file

@ -49,13 +49,15 @@ Future<void> 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<void> updateLastMessageId(int fromUserId, String messageId) async {
}
Future<PushNotification?> getPushNotificationFromEncryptedContent(
int toUserId,
int? toUserId,
String? messageId,
EncryptedContent content,
) async {
@ -210,7 +212,7 @@ Future<PushNotification?> 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<PushNotification?> 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) {

View file

@ -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));

View file

@ -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<HomeView> {
});
};
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<HomeView> {
}
Future<void> 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<HomeView> {
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: <Widget>[