mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-04-18 17:32:54 +00:00
opening chat if clicked on the notification
This commit is contained in:
parent
aa26766bdf
commit
267e2bd376
12 changed files with 200 additions and 94 deletions
|
|
@ -3,6 +3,7 @@
|
||||||
## 0.1.2
|
## 0.1.2
|
||||||
|
|
||||||
- New: Developer settings to reduce flames
|
- 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
|
- Improve: Improved troubleshooting for issues with push notifications
|
||||||
- Fix: Flash not activated when starting a video recording
|
- Fix: Flash not activated when starting a video recording
|
||||||
- Fix: Problem sending media when a recipient has deleted their account.
|
- Fix: Problem sending media when a recipient has deleted their account.
|
||||||
|
|
|
||||||
|
|
@ -56,13 +56,20 @@ PODS:
|
||||||
- FirebaseAnalytics (~> 12.9.0)
|
- FirebaseAnalytics (~> 12.9.0)
|
||||||
- Firebase/CoreOnly (12.9.0):
|
- Firebase/CoreOnly (12.9.0):
|
||||||
- FirebaseCore (~> 12.9.0)
|
- FirebaseCore (~> 12.9.0)
|
||||||
|
- Firebase/Installations (12.9.0):
|
||||||
|
- Firebase/CoreOnly
|
||||||
|
- FirebaseInstallations (~> 12.9.0)
|
||||||
- Firebase/Messaging (12.9.0):
|
- Firebase/Messaging (12.9.0):
|
||||||
- Firebase/CoreOnly
|
- Firebase/CoreOnly
|
||||||
- FirebaseMessaging (~> 12.9.0)
|
- 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)
|
- Firebase/CoreOnly (= 12.9.0)
|
||||||
- Flutter
|
- Flutter
|
||||||
- firebase_messaging (16.1.2):
|
- firebase_messaging (16.1.3):
|
||||||
- Firebase/Messaging (= 12.9.0)
|
- Firebase/Messaging (= 12.9.0)
|
||||||
- firebase_core
|
- firebase_core
|
||||||
- Flutter
|
- Flutter
|
||||||
|
|
@ -278,17 +285,17 @@ PODS:
|
||||||
- PromisesObjC (2.4.0)
|
- PromisesObjC (2.4.0)
|
||||||
- restart_app (1.7.3):
|
- restart_app (1.7.3):
|
||||||
- Flutter
|
- Flutter
|
||||||
- SDWebImage (5.21.6):
|
- SDWebImage (5.21.7):
|
||||||
- SDWebImage/Core (= 5.21.6)
|
- SDWebImage/Core (= 5.21.7)
|
||||||
- SDWebImage/Core (5.21.6)
|
- SDWebImage/Core (5.21.7)
|
||||||
- SDWebImageWebPCoder (0.15.0):
|
- SDWebImageWebPCoder (0.15.0):
|
||||||
- libwebp (~> 1.0)
|
- libwebp (~> 1.0)
|
||||||
- SDWebImage/Core (~> 5.17)
|
- SDWebImage/Core (~> 5.17)
|
||||||
- Sentry/HybridSDK (8.56.2)
|
- Sentry/HybridSDK (8.58.0)
|
||||||
- sentry_flutter (9.14.0):
|
- sentry_flutter (9.16.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- Sentry/HybridSDK (= 8.56.2)
|
- Sentry/HybridSDK (= 8.58.0)
|
||||||
- share_plus (0.0.1):
|
- share_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- shared_preferences_foundation (0.0.1):
|
- shared_preferences_foundation (0.0.1):
|
||||||
|
|
@ -297,32 +304,32 @@ PODS:
|
||||||
- sqflite_darwin (0.0.4):
|
- sqflite_darwin (0.0.4):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- sqlite3 (3.51.1):
|
- sqlite3 (3.52.0):
|
||||||
- sqlite3/common (= 3.51.1)
|
- sqlite3/common (= 3.52.0)
|
||||||
- sqlite3/common (3.51.1)
|
- sqlite3/common (3.52.0)
|
||||||
- sqlite3/dbstatvtab (3.51.1):
|
- sqlite3/dbstatvtab (3.52.0):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/fts5 (3.51.1):
|
- sqlite3/fts5 (3.52.0):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/math (3.51.1):
|
- sqlite3/math (3.52.0):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/perf-threadsafe (3.51.1):
|
- sqlite3/perf-threadsafe (3.52.0):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/rtree (3.51.1):
|
- sqlite3/rtree (3.52.0):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/session (3.51.1):
|
- sqlite3/session (3.52.0):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3_flutter_libs (0.0.1):
|
- sqlite3_flutter_libs (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- sqlite3 (~> 3.51.1)
|
- sqlite3 (~> 3.52.0)
|
||||||
- sqlite3/dbstatvtab
|
- sqlite3/dbstatvtab
|
||||||
- sqlite3/fts5
|
- sqlite3/fts5
|
||||||
- sqlite3/math
|
- sqlite3/math
|
||||||
- sqlite3/perf-threadsafe
|
- sqlite3/perf-threadsafe
|
||||||
- sqlite3/rtree
|
- sqlite3/rtree
|
||||||
- sqlite3/session
|
- sqlite3/session
|
||||||
- SwiftProtobuf (1.34.1)
|
- SwiftProtobuf (1.36.1)
|
||||||
- SwiftyGif (5.4.5)
|
- SwiftyGif (5.4.5)
|
||||||
- url_launcher_ios (0.0.1):
|
- url_launcher_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
|
@ -343,6 +350,7 @@ DEPENDENCIES:
|
||||||
- emoji_picker_flutter (from `.symlinks/plugins/emoji_picker_flutter/ios`)
|
- emoji_picker_flutter (from `.symlinks/plugins/emoji_picker_flutter/ios`)
|
||||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||||
- Firebase
|
- Firebase
|
||||||
|
- firebase_app_installations (from `.symlinks/plugins/firebase_app_installations/ios`)
|
||||||
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
||||||
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
||||||
- FirebaseCore
|
- FirebaseCore
|
||||||
|
|
@ -430,6 +438,8 @@ EXTERNAL SOURCES:
|
||||||
:path: ".symlinks/plugins/emoji_picker_flutter/ios"
|
:path: ".symlinks/plugins/emoji_picker_flutter/ios"
|
||||||
file_picker:
|
file_picker:
|
||||||
:path: ".symlinks/plugins/file_picker/ios"
|
:path: ".symlinks/plugins/file_picker/ios"
|
||||||
|
firebase_app_installations:
|
||||||
|
:path: ".symlinks/plugins/firebase_app_installations/ios"
|
||||||
firebase_core:
|
firebase_core:
|
||||||
:path: ".symlinks/plugins/firebase_core/ios"
|
:path: ".symlinks/plugins/firebase_core/ios"
|
||||||
firebase_messaging:
|
firebase_messaging:
|
||||||
|
|
@ -493,7 +503,7 @@ SPEC CHECKSUMS:
|
||||||
app_links: a754cbec3c255bd4bbb4d236ecc06f28cd9a7ce8
|
app_links: a754cbec3c255bd4bbb4d236ecc06f28cd9a7ce8
|
||||||
audio_waveforms: a6dde7fe7c0ea05f06ffbdb0f7c1b2b2ba6cedcf
|
audio_waveforms: a6dde7fe7c0ea05f06ffbdb0f7c1b2b2ba6cedcf
|
||||||
background_downloader: 50e91d979067b82081aba359d7d916b3ba5fadad
|
background_downloader: 50e91d979067b82081aba359d7d916b3ba5fadad
|
||||||
camera_avfoundation: 5675ca25298b6f81fa0a325188e7df62cc217741
|
camera_avfoundation: 968a9a5323c79a99c166ad9d7866bfd2047b5a9b
|
||||||
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
||||||
cryptography_flutter_plus: 44f4e9e4079395fcbb3e7809c0ac2c6ae2d9576f
|
cryptography_flutter_plus: 44f4e9e4079395fcbb3e7809c0ac2c6ae2d9576f
|
||||||
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
||||||
|
|
@ -502,8 +512,9 @@ SPEC CHECKSUMS:
|
||||||
emoji_picker_flutter: ece213fc274bdddefb77d502d33080dc54e616cc
|
emoji_picker_flutter: ece213fc274bdddefb77d502d33080dc54e616cc
|
||||||
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
||||||
Firebase: 065f2bb395062046623036d8e6dc857bc2521d56
|
Firebase: 065f2bb395062046623036d8e6dc857bc2521d56
|
||||||
firebase_core: afac1aac13c931e0401c7e74ed1276112030efab
|
firebase_app_installations: 1abd8d071ea2022d7888f7a9713710c37136ff91
|
||||||
firebase_messaging: 7cb2727feb789751fc6936bcc8e08408970e2820
|
firebase_core: 8e6f58412ca227827c366b92e7cee047a2148c60
|
||||||
|
firebase_messaging: c3aa897e0d40109cfb7927c40dc0dea799863f3b
|
||||||
FirebaseAnalytics: cd7d01d352f3c237c9a0e31552c257cd0b0c0352
|
FirebaseAnalytics: cd7d01d352f3c237c9a0e31552c257cd0b0c0352
|
||||||
FirebaseCore: 428912f751178b06bef0a1793effeb4a5e09a9b8
|
FirebaseCore: 428912f751178b06bef0a1793effeb4a5e09a9b8
|
||||||
FirebaseCoreInternal: b321eafae5362113bc182956fafc9922cfc77b72
|
FirebaseCoreInternal: b321eafae5362113bc182956fafc9922cfc77b72
|
||||||
|
|
@ -544,16 +555,16 @@ SPEC CHECKSUMS:
|
||||||
pro_video_editor: 44ef9a6d48dbd757ed428cf35396dd05f35c7830
|
pro_video_editor: 44ef9a6d48dbd757ed428cf35396dd05f35c7830
|
||||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||||
restart_app: 0714144901e260eae68f7afc2fc4aacc1a323ad2
|
restart_app: 0714144901e260eae68f7afc2fc4aacc1a323ad2
|
||||||
SDWebImage: 1bb6a1b84b6fe87b972a102bdc77dd589df33477
|
SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf
|
||||||
SDWebImageWebPCoder: 0e06e365080397465cc73a7a9b472d8a3bd0f377
|
SDWebImageWebPCoder: 0e06e365080397465cc73a7a9b472d8a3bd0f377
|
||||||
Sentry: b53951377b78e21a734f5dc8318e333dbfc682d7
|
Sentry: d587a8fe91ca13503ecd69a1905f3e8a0fcf61be
|
||||||
sentry_flutter: 841fa2fe08dc72eb95e2320b76e3f751f3400cf5
|
sentry_flutter: 31101687061fb85211ebab09ce6eb8db4e9ba74f
|
||||||
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
||||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||||
sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b
|
sqlite3: a51c07cf16e023d6c48abd5e5791a61a47354921
|
||||||
sqlite3_flutter_libs: d13b8b3003f18f596e542bcb9482d105577eff41
|
sqlite3_flutter_libs: b3e120efe9a82017e5552a620f696589ed4f62ab
|
||||||
SwiftProtobuf: c901f00a3e125dc33cac9b16824da85682ee47da
|
SwiftProtobuf: 9e106a71456f4d3f6a3b0c8fd87ef0be085efc38
|
||||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||||
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
||||||
video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a
|
video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a
|
||||||
|
|
|
||||||
|
|
@ -133,9 +133,7 @@ class ApiService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
reconnectionTimer?.cancel();
|
reconnectionTimer?.cancel();
|
||||||
Log.info('Starting reconnection timer with $_reconnectionDelay s delay');
|
|
||||||
reconnectionTimer = Timer(Duration(seconds: _reconnectionDelay), () async {
|
reconnectionTimer = Timer(Duration(seconds: _reconnectionDelay), () async {
|
||||||
Log.info('Reconnection timer triggered');
|
|
||||||
reconnectionTimer = null;
|
reconnectionTimer = null;
|
||||||
// only try to reconnect in case the app is in the foreground
|
// only try to reconnect in case the app is in the foreground
|
||||||
if (!globalIsAppInBackground) {
|
if (!globalIsAppInBackground) {
|
||||||
|
|
|
||||||
|
|
@ -111,8 +111,8 @@ Future<(Uint8List, Uint8List?)?> tryToSendCompleteMessage({
|
||||||
);
|
);
|
||||||
|
|
||||||
Uint8List? pushData;
|
Uint8List? pushData;
|
||||||
if (pushNotification != null && receipt.retryCount <= 3) {
|
if (pushNotification != null && receipt.retryCount <= 1) {
|
||||||
/// In case the message has to be resend more than three times, do not show a notification again...
|
// Only show the push notification the first two time.
|
||||||
pushData = await encryptPushNotification(
|
pushData = await encryptPushNotification(
|
||||||
receipt.contactId,
|
receipt.contactId,
|
||||||
pushNotification,
|
pushNotification,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:hashlib/random.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/client2client/text_message.c2c.dart';
|
||||||
import 'package:twonly/src/services/api/messages.dart';
|
import 'package:twonly/src/services/api/messages.dart';
|
||||||
import 'package:twonly/src/services/group.services.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/encryption.signal.dart';
|
||||||
import 'package:twonly/src/services/signal/session.signal.dart';
|
import 'package:twonly/src/services/signal/session.signal.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
@ -164,7 +166,7 @@ Future<void> handleClient2ClientMessage(NewMessage newMessage) async {
|
||||||
final (
|
final (
|
||||||
encryptedContent,
|
encryptedContent,
|
||||||
plainTextContent,
|
plainTextContent,
|
||||||
) = await handleEncryptedMessage(
|
) = await handleEncryptedMessageRaw(
|
||||||
fromUserId,
|
fromUserId,
|
||||||
encryptedContentRaw,
|
encryptedContentRaw,
|
||||||
message.type,
|
message.type,
|
||||||
|
|
@ -182,6 +184,9 @@ Future<void> handleClient2ClientMessage(NewMessage newMessage) async {
|
||||||
encryptedContent: encryptedContent.writeToBuffer(),
|
encryptedContent: encryptedContent.writeToBuffer(),
|
||||||
);
|
);
|
||||||
receiptIdDB = const Value.absent();
|
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,
|
int fromUserId,
|
||||||
Uint8List encryptedContentRaw,
|
Uint8List encryptedContentRaw,
|
||||||
Message_Type messageType,
|
Message_Type messageType,
|
||||||
String receiptId,
|
String receiptId,
|
||||||
) async {
|
) async {
|
||||||
final (content, decryptionErrorType) = await signalDecryptMessage(
|
final (encryptedContent, decryptionErrorType) = await signalDecryptMessage(
|
||||||
fromUserId,
|
fromUserId,
|
||||||
encryptedContentRaw,
|
encryptedContentRaw,
|
||||||
messageType.value,
|
messageType.value,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (content == null) {
|
if (encryptedContent == null) {
|
||||||
return (
|
return (
|
||||||
null,
|
null,
|
||||||
PlaintextContent()
|
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
|
// 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
|
// 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).
|
// will be either transmitted again after a new server connection (minimum 20 seconds).
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,6 @@ bool isItPossibleToRestoreFlames(Group group) {
|
||||||
return group.maxFlameCounter > 2 &&
|
return group.maxFlameCounter > 2 &&
|
||||||
flameCounter < group.maxFlameCounter &&
|
flameCounter < group.maxFlameCounter &&
|
||||||
group.maxFlameCounterFrom!.isAfter(
|
group.maxFlameCounterFrom!.isAfter(
|
||||||
clock.now().subtract(const Duration(days: 5)),
|
clock.now().subtract(const Duration(days: 7)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,13 +72,19 @@ Future<void> compressAndOverlayVideo(MediaFileService media) async {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final task = VideoRenderData(
|
final task = VideoRenderData(
|
||||||
video: EditorVideo.file(media.originalPath),
|
videoSegments: [
|
||||||
imageBytes: media.overlayImagePath.readAsBytesSync(),
|
VideoSegment(video: EditorVideo.file(media.originalPath)),
|
||||||
|
],
|
||||||
|
imageLayers: [
|
||||||
|
ImageLayer(image: EditorLayerImage.file(media.overlayImagePath)),
|
||||||
|
],
|
||||||
enableAudio: !media.removeAudio,
|
enableAudio: !media.removeAudio,
|
||||||
);
|
);
|
||||||
|
|
||||||
await ProVideoEditor.instance
|
await ProVideoEditor.instance.renderVideoToFile(
|
||||||
.renderVideoToFile(media.ffmpegOutputPath.path, task);
|
media.ffmpegOutputPath.path,
|
||||||
|
task,
|
||||||
|
);
|
||||||
|
|
||||||
if (Platform.isIOS ||
|
if (Platform.isIOS ||
|
||||||
media.ffmpegOutputPath.statSync().size >= 10_000_000 ||
|
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)
|
final sizeFrom = (media.ffmpegOutputPath.statSync().size / 1024 / 1024)
|
||||||
.toStringAsFixed(2);
|
.toStringAsFixed(2);
|
||||||
final sizeTo =
|
final sizeTo = (media.tempPath.statSync().size / 1024 / 1024)
|
||||||
(media.tempPath.statSync().size / 1024 / 1024).toStringAsFixed(2);
|
.toStringAsFixed(2);
|
||||||
|
|
||||||
Log.info(
|
Log.info(
|
||||||
'It took ${stopwatch.elapsedMilliseconds}ms to compress the video. Reduced from $sizeFrom to $sizeTo bytes.',
|
'It took ${stopwatch.elapsedMilliseconds}ms to compress the video. Reduced from $sizeFrom to $sizeTo bytes.',
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,12 @@ import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart';
|
||||||
import 'package:cryptography_plus/cryptography_plus.dart';
|
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:path_provider/path_provider.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/constants/secure_storage_keys.dart';
|
||||||
import 'package:twonly/src/localization/generated/app_localizations.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_de.dart';
|
||||||
import 'package:twonly/src/localization/generated/app_localizations_en.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/model/protobuf/client/generated/push_notification.pb.dart';
|
||||||
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
||||||
import 'package:twonly/src/utils/log.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 {
|
Future<void> handlePushData(String pushDataB64) async {
|
||||||
try {
|
try {
|
||||||
final pushData =
|
final pushData = EncryptedPushNotification.fromBuffer(
|
||||||
EncryptedPushNotification.fromBuffer(base64.decode(pushDataB64));
|
base64.decode(pushDataB64),
|
||||||
|
);
|
||||||
|
|
||||||
PushNotification? pushNotification;
|
PushNotification? pushNotification;
|
||||||
PushUser? foundPushUser;
|
PushUser? foundPushUser;
|
||||||
|
|
@ -121,8 +147,10 @@ Future<PushNotification?> tryDecryptMessage(
|
||||||
mac: Mac(push.mac),
|
mac: Mac(push.mac),
|
||||||
);
|
);
|
||||||
|
|
||||||
final plaintext =
|
final plaintext = await chacha20.decrypt(
|
||||||
await chacha20.decrypt(secretBox, secretKey: secretKeyData);
|
secretBox,
|
||||||
|
secretKey: secretKeyData,
|
||||||
|
);
|
||||||
return PushNotification.fromBuffer(plaintext);
|
return PushNotification.fromBuffer(plaintext);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// this error is allowed to happen...
|
// this error is allowed to happen...
|
||||||
|
|
@ -132,8 +160,9 @@ Future<PushNotification?> tryDecryptMessage(
|
||||||
|
|
||||||
Future<void> showLocalPushNotification(
|
Future<void> showLocalPushNotification(
|
||||||
PushUser pushUser,
|
PushUser pushUser,
|
||||||
PushNotification pushNotification,
|
PushNotification pushNotification, {
|
||||||
) async {
|
String? groupId,
|
||||||
|
}) async {
|
||||||
String? title;
|
String? title;
|
||||||
String? body;
|
String? body;
|
||||||
|
|
||||||
|
|
@ -174,13 +203,25 @@ Future<void> showLocalPushNotification(
|
||||||
iOS: darwinNotificationDetails,
|
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(
|
await flutterLocalNotificationsPlugin.show(
|
||||||
pushUser.userId.toInt() %
|
// Invalid argument (id): must fit within the size of a 32-bit integer
|
||||||
2147483647, // Invalid argument (id): must fit within the size of a 32-bit integer
|
pushUser.userId.toInt() % 2147483647,
|
||||||
title,
|
title,
|
||||||
body,
|
body,
|
||||||
notificationDetails,
|
notificationDetails,
|
||||||
// payload: pushNotification.kind.name,
|
payload: payload,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -259,17 +300,22 @@ String getPushNotificationText(PushNotification pushNotification) {
|
||||||
PushKind.storedMediaFile.name: lang.notificationStoredMediaFile,
|
PushKind.storedMediaFile.name: lang.notificationStoredMediaFile,
|
||||||
PushKind.reaction.name: lang.notificationReaction,
|
PushKind.reaction.name: lang.notificationReaction,
|
||||||
PushKind.reopenedMedia.name: lang.notificationReopenedMedia,
|
PushKind.reopenedMedia.name: lang.notificationReopenedMedia,
|
||||||
PushKind.reactionToVideo.name:
|
PushKind.reactionToVideo.name: lang.notificationReactionToVideo(
|
||||||
lang.notificationReactionToVideo(pushNotification.additionalContent),
|
pushNotification.additionalContent,
|
||||||
PushKind.reactionToAudio.name:
|
),
|
||||||
lang.notificationReactionToAudio(pushNotification.additionalContent),
|
PushKind.reactionToAudio.name: lang.notificationReactionToAudio(
|
||||||
PushKind.reactionToText.name:
|
pushNotification.additionalContent,
|
||||||
lang.notificationReactionToText(pushNotification.additionalContent),
|
),
|
||||||
PushKind.reactionToImage.name:
|
PushKind.reactionToText.name: lang.notificationReactionToText(
|
||||||
lang.notificationReactionToImage(pushNotification.additionalContent),
|
pushNotification.additionalContent,
|
||||||
|
),
|
||||||
|
PushKind.reactionToImage.name: lang.notificationReactionToImage(
|
||||||
|
pushNotification.additionalContent,
|
||||||
|
),
|
||||||
PushKind.response.name: lang.notificationResponse(inGroup),
|
PushKind.response.name: lang.notificationResponse(inGroup),
|
||||||
PushKind.addedToGroup.name:
|
PushKind.addedToGroup.name: lang.notificationAddedToGroup(
|
||||||
lang.notificationAddedToGroup(pushNotification.additionalContent),
|
pushNotification.additionalContent,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
return pushNotificationText[pushNotification.kind.name] ?? '';
|
return pushNotificationText[pushNotification.kind.name] ?? '';
|
||||||
|
|
|
||||||
|
|
@ -110,35 +110,23 @@ Future<void> initFCMService() async {
|
||||||
options: DefaultFirebaseOptions.currentPlatform,
|
options: DefaultFirebaseOptions.currentPlatform,
|
||||||
);
|
);
|
||||||
|
|
||||||
unawaited(checkForTokenUpdates());
|
await checkForTokenUpdates();
|
||||||
|
|
||||||
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
|
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();
|
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);
|
FirebaseMessaging.onMessage.listen(handleRemoteMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@pragma('vm:entry-point')
|
@pragma('vm:entry-point')
|
||||||
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
||||||
initLogger();
|
final isInitialized = await initBackgroundExecution();
|
||||||
// Log.info('Handling a background message: ${message.messageId}');
|
Log.info('Handling a background message: ${message.messageId}');
|
||||||
await handleRemoteMessage(message);
|
await handleRemoteMessage(message);
|
||||||
|
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
if (await initBackgroundExecution()) {
|
if (isInitialized) {
|
||||||
await handlePeriodicTask();
|
await handlePeriodicTask();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -164,7 +152,11 @@ Future<void> handleRemoteMessage(RemoteMessage message) async {
|
||||||
final body =
|
final body =
|
||||||
message.notification?.body ?? message.data['body'] as String? ?? '';
|
message.notification?.body ?? message.data['body'] as String? ?? '';
|
||||||
await customLocalPushNotification(title, body);
|
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);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,13 +49,15 @@ Future<void> setupNotificationWithUsers({
|
||||||
|
|
||||||
final contacts = await twonlyDB.contactsDao.getAllContacts();
|
final contacts = await twonlyDB.contactsDao.getAllContacts();
|
||||||
for (final contact in contacts) {
|
for (final contact in contacts) {
|
||||||
final pushUser =
|
final pushUser = pushUsers.firstWhereOrNull(
|
||||||
pushUsers.firstWhereOrNull((x) => x.userId == contact.userId);
|
(x) => x.userId == contact.userId,
|
||||||
|
);
|
||||||
|
|
||||||
if (pushUser != null && pushUser.pushKeys.isNotEmpty) {
|
if (pushUser != null && pushUser.pushKeys.isNotEmpty) {
|
||||||
// make it harder to predict the change of the key
|
// make it harder to predict the change of the key
|
||||||
final timeBefore =
|
final timeBefore = clock.now().subtract(
|
||||||
clock.now().subtract(Duration(days: 10 + random.nextInt(5)));
|
Duration(days: 10 + random.nextInt(5)),
|
||||||
|
);
|
||||||
final lastKey = pushUser.pushKeys.last;
|
final lastKey = pushUser.pushKeys.last;
|
||||||
final createdAt = DateTime.fromMillisecondsSinceEpoch(
|
final createdAt = DateTime.fromMillisecondsSinceEpoch(
|
||||||
lastKey.createdAtUnixTimestamp.toInt(),
|
lastKey.createdAtUnixTimestamp.toInt(),
|
||||||
|
|
@ -197,7 +199,7 @@ Future<void> updateLastMessageId(int fromUserId, String messageId) async {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<PushNotification?> getPushNotificationFromEncryptedContent(
|
Future<PushNotification?> getPushNotificationFromEncryptedContent(
|
||||||
int toUserId,
|
int? toUserId,
|
||||||
String? messageId,
|
String? messageId,
|
||||||
EncryptedContent content,
|
EncryptedContent content,
|
||||||
) async {
|
) async {
|
||||||
|
|
@ -210,7 +212,7 @@ Future<PushNotification?> getPushNotificationFromEncryptedContent(
|
||||||
final msg = await twonlyDB.messagesDao
|
final msg = await twonlyDB.messagesDao
|
||||||
.getMessageById(content.reaction.targetMessageId)
|
.getMessageById(content.reaction.targetMessageId)
|
||||||
.getSingleOrNull();
|
.getSingleOrNull();
|
||||||
if (msg == null || msg.senderId == null || msg.senderId != toUserId) {
|
if (msg == null || msg.senderId != toUserId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (msg.content != null) {
|
if (msg.content != null) {
|
||||||
|
|
@ -285,7 +287,7 @@ Future<PushNotification?> getPushNotificationFromEncryptedContent(
|
||||||
.getMessageById(content.reaction.targetMessageId)
|
.getMessageById(content.reaction.targetMessageId)
|
||||||
.getSingleOrNull();
|
.getSingleOrNull();
|
||||||
// These notifications should only be send to the original sender.
|
// 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;
|
return null;
|
||||||
}
|
}
|
||||||
switch (content.mediaUpdate.type) {
|
switch (content.mediaUpdate.type) {
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,11 @@ import 'package:sentry_flutter/sentry_flutter.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/utils/exclusive_access.dart';
|
import 'package:twonly/src/utils/exclusive_access.dart';
|
||||||
|
|
||||||
|
bool _isInitialized = false;
|
||||||
|
|
||||||
void initLogger() {
|
void initLogger() {
|
||||||
// Logger.root.level = kReleaseMode ? Level.INFO : Level.ALL;
|
if (_isInitialized) return;
|
||||||
|
_isInitialized = true;
|
||||||
Logger.root.level = Level.ALL;
|
Logger.root.level = Level.ALL;
|
||||||
Logger.root.onRecord.listen((record) async {
|
Logger.root.onRecord.listen((record) async {
|
||||||
unawaited(_writeLogToFile(record));
|
unawaited(_writeLogToFile(record));
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_sharing_intent/model/sharing_file.dart';
|
import 'package:flutter_sharing_intent/model/sharing_file.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:twonly/globals.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/intent/links.intent.dart';
|
||||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||||
import 'package:twonly/src/services/notifications/setup.notifications.dart';
|
import 'package:twonly/src/services/notifications/setup.notifications.dart';
|
||||||
|
|
@ -106,7 +108,12 @@ class HomeViewState extends State<HomeView> {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
selectNotificationStream.stream.listen((response) async {
|
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(_mainCameraController.selectCamera(0, true));
|
||||||
unawaited(initAsync());
|
unawaited(initAsync());
|
||||||
|
|
@ -140,13 +147,26 @@ class HomeViewState extends State<HomeView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
final notificationAppLaunchDetails =
|
final notificationAppLaunchDetails = await flutterLocalNotificationsPlugin
|
||||||
await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
|
.getNotificationAppLaunchDetails();
|
||||||
|
|
||||||
if (widget.initialPage == 0 ||
|
if (widget.initialPage == 0 ||
|
||||||
(notificationAppLaunchDetails != null &&
|
(notificationAppLaunchDetails != null &&
|
||||||
notificationAppLaunchDetails.didNotificationLaunchApp)) {
|
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();
|
final draftMedia = await twonlyDB.mediaFilesDao.getDraftMediaFile();
|
||||||
|
|
@ -168,8 +188,9 @@ class HomeViewState extends State<HomeView> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: GestureDetector(
|
body: GestureDetector(
|
||||||
onDoubleTap:
|
onDoubleTap: offsetRatio == 0
|
||||||
offsetRatio == 0 ? _mainCameraController.onDoubleTap : null,
|
? _mainCameraController.onDoubleTap
|
||||||
|
: null,
|
||||||
onTapDown: offsetRatio == 0 ? _mainCameraController.onTapDown : null,
|
onTapDown: offsetRatio == 0 ? _mainCameraController.onTapDown : null,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue