diff --git a/.gitignore b/.gitignore index 4314849..0ebd653 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related diff --git a/lib/src/app.dart b/lib/src/app.dart index 9e737a6..de2291d 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -120,57 +120,6 @@ class _MyAppState extends State with WidgetsBindingObserver { } } - void _initService() { - FlutterForegroundTask.init( - androidNotificationOptions: AndroidNotificationOptions( - channelId: 'foreground_service', - channelName: 'Foreground Service Notification', - channelDescription: - 'This notification appears when the foreground service is running.', - onlyAlertOnce: true, - ), - iosNotificationOptions: const IOSNotificationOptions( - showNotification: false, - playSound: false, - ), - foregroundTaskOptions: ForegroundTaskOptions( - eventAction: ForegroundTaskEventAction.repeat(5000), - autoRunOnBoot: true, - autoRunOnMyPackageReplaced: true, - allowWakeLock: true, - allowWifiLock: true, - ), - ); - } - - Future _startService() async { - if (await FlutterForegroundTask.isRunningService) { - return FlutterForegroundTask.restartService(); - } else { - return FlutterForegroundTask.startService( - serviceId: 256, - notificationTitle: 'Staying connected to the server.', - notificationText: 'Tap to return to the app', - notificationIcon: - NotificationIcon(metaDataName: "eu.twonly.service.TWONLY_LOGO"), - notificationInitialRoute: '/chats', - callback: startCallback, - ); - } - } - - Future _stopService() async { - if (context.mounted) { - context.read().init(afterPaused: true); - context.read().update(); - } - FlutterForegroundTask.sendDataToTask(""); - await FlutterForegroundTask.stopService(); - if (!apiProvider.isAuthenticated) { - apiProvider.connect(); - } - } - @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); @@ -178,15 +127,18 @@ class _MyAppState extends State with WidgetsBindingObserver { if (wasPaused) { globalIsAppInBackground = false; apiProvider.connect(); + context.read().update(); + context.read().init(); // _stopService(); } } else if (state == AppLifecycleState.paused) { wasPaused = true; globalIsAppInBackground = true; - apiProvider.close(() { - // use this only when uploading an image - // _startService(); - }); + + // apiProvider.close(() { + // use this only when uploading an image + // _startService(); + // }); } } diff --git a/lib/src/localization/app_de.arb b/lib/src/localization/app_de.arb index 3ed667d..fb9b3d6 100644 --- a/lib/src/localization/app_de.arb +++ b/lib/src/localization/app_de.arb @@ -34,6 +34,7 @@ "searchUsernameInput": "Benutzername", "searchUsernameTitle": "Benutzernamen suchen", "searchUsernameNotFound": "Benutzername nicht gefunden", + "searchUsernameNotFoundBody": "Es wurde kein Benutzer mit dem Benutzernamen \"{username}\" gefunden.", "searchUsernameNewFollowerTitle": "Folgeanfragen", "searchUsernameQrCodeBtn": "QR-Code scannen", "chatListViewSearchUserNameBtn": "Füge deinen ersten twonly-Kontakt hinzu!", diff --git a/lib/src/localization/app_en.arb b/lib/src/localization/app_en.arb index 9202ddf..d8ad95f 100644 --- a/lib/src/localization/app_en.arb +++ b/lib/src/localization/app_en.arb @@ -34,6 +34,7 @@ "searchUsernameInput": "Username", "searchUsernameTitle": "Search username", "searchUsernameNotFound": "Username not found", + "searchUsernameNotFoundBody": "There is no user with the username \"{username}\" registered.", "searchUsernameNewFollowerTitle": "Follow requests", "searchUsernameQrCodeBtn": "Scan QR code", "chatListViewSearchUserNameBtn": "Add your first twonly contact!", diff --git a/lib/src/model/messages_model.dart b/lib/src/model/messages_model.dart index 53415c4..ea442d6 100644 --- a/lib/src/model/messages_model.dart +++ b/lib/src/model/messages_model.dart @@ -325,7 +325,7 @@ class DbMessages extends CvModelBase { } static Future userOpenedOtherMessage( - int otherMessageId, int fromUserId) async { + int fromUserId, int otherMessageId) async { Map data = { columnMessageOpenedAt: DateTime.now().toIso8601String(), }; diff --git a/lib/src/providers/api/api.dart b/lib/src/providers/api/api.dart index b94f225..097fed6 100644 --- a/lib/src/providers/api/api.dart +++ b/lib/src/providers/api/api.dart @@ -19,13 +19,13 @@ import 'package:twonly/src/utils/signal.dart' as SignalHelper; Future tryTransmitMessages() async { List retransmit = await DbMessages.getAllMessagesForRetransmitting(); + if (retransmit.isEmpty) return; - debugPrint("tryTransmitMessages: ${retransmit.length}"); + Logger("api.dart").info("try sending messages: ${retransmit.length}"); Box box = await getMediaStorage(); for (int i = 0; i < retransmit.length; i++) { int msgId = retransmit[i].messageId; - debugPrint("msgId=$msgId"); Uint8List? bytes = box.get("retransmit-$msgId-textmessage"); if (bytes != null) { @@ -46,13 +46,8 @@ Future tryTransmitMessages() async { if (encryptedMedia != null) { final content = retransmit[i].messageContent; if (content is MediaMessageContent) { - await uploadMediaFile( - msgId, - Int64(retransmit[i].otherUserId), - encryptedMedia, - content.isRealTwonly, - content.maxShowTime, - retransmit[i].sendAt); + uploadMediaFile(msgId, Int64(retransmit[i].otherUserId), encryptedMedia, + content.isRealTwonly, content.maxShowTime, retransmit[i].sendAt); } } } @@ -69,7 +64,6 @@ Future encryptAndSendMessage(Int64 userId, Message msg) async { Box box = await getMediaStorage(); if (msg.messageId != null) { - debugPrint("putting=${msg.messageId}"); box.put("retransmit-${msg.messageId}-textmessage", bytes); } @@ -118,6 +112,7 @@ Future uploadMediaFile( DateTime messageSendAt, ) async { Box box = await getMediaStorage(); + Logger("api.dart").info("Uploading image $messageId"); List? uploadToken = box.get("retransmit-$messageId-uploadtoken"); if (uploadToken == null) { @@ -135,10 +130,10 @@ Future uploadMediaFile( int offset = box.get("retransmit-$messageId-offset") ?? 0; - int fragmentedTransportSize = 100000; + int fragmentedTransportSize = 50000; while (offset < encryptedMedia.length) { - debugPrint("offset: $offset"); + Logger("api.dart").info("Uploading image $messageId with offset: $offset"); int end = encryptedMedia.length; if (offset + fragmentedTransportSize < encryptedMedia.length) { end = offset + fragmentedTransportSize; @@ -162,8 +157,6 @@ Future uploadMediaFile( offset = end; } - Logger("api.dart").shout("DOING UPDATE"); - box.delete("retransmit-$messageId-media"); box.delete("retransmit-$messageId-uploadtoken"); @@ -302,7 +295,7 @@ Future tryDownloadMedia(int messageId, int fromUserId, List mediaToken, } Future userOpenedOtherMessage(int fromUserId, int messageOtherId) async { - await DbMessages.userOpenedOtherMessage(messageOtherId, fromUserId); + await DbMessages.userOpenedOtherMessage(fromUserId, messageOtherId); encryptAndSendMessage( Int64(fromUserId), diff --git a/lib/src/providers/api/server_messages.dart b/lib/src/providers/api/server_messages.dart index e2f53e8..736df6c 100644 --- a/lib/src/providers/api/server_messages.dart +++ b/lib/src/providers/api/server_messages.dart @@ -53,7 +53,8 @@ Future handleDownloadData(DownloadData data) async { // download should only be done when the app is open return client.Response()..error = ErrorCode.InternalError; } - debugPrint("Downloading: ${data.uploadToken} ${data.fin}"); + Logger("server_messages") + .info("downloading: ${data.uploadToken} ${data.fin}"); final box = await getMediaStorage(); String boxId = data.uploadToken.toString(); @@ -83,6 +84,8 @@ Future handleDownloadData(DownloadData data) async { Uint8List downloadedBytes; if (buffered != null) { if (data.offset != buffered.length) { + Logger("server_messages") + .info("server send wrong offset: ${data.offset} ${buffered.length}"); // Logger("handleDownloadData").error(object) return client.Response()..error = ErrorCode.InvalidOffset; } @@ -104,6 +107,9 @@ Future handleDownloadData(DownloadData data) async { if (rawBytes != null) { box.put("${data.uploadToken}_downloaded", rawBytes); + } else { + Logger("server_messages") + .shout("error decrypting the message: ${data.uploadToken}"); } box.delete(boxId); diff --git a/lib/src/providers/api_provider.dart b/lib/src/providers/api_provider.dart index a51ea13..577507e 100644 --- a/lib/src/providers/api_provider.dart +++ b/lib/src/providers/api_provider.dart @@ -25,11 +25,11 @@ import 'package:web_socket_channel/web_socket_channel.dart'; /// It handles errors and does automatically tries to reconnect on /// errors or network changes. class ApiProvider { - final String apiUrl = kDebugMode + final String apiUrl = (kDebugMode && false) ? "ws://10.99.0.6:3030/api/client" : "wss://api.twonly.eu/api/client"; // ws://api.twonly.eu/api/client - final String? backupApiUrl = kDebugMode + final String? backupApiUrl = (kDebugMode && false) ? "ws://10.99.0.6:3030/api/client" : "wss://api2.twonly.eu/api/client"; bool isAuthenticated = false; @@ -70,10 +70,13 @@ class ApiProvider { globalCallbackConnectionState(true); _reconnectionDelay = 5; - tryTransmitMessages(); + if (!globalIsAppInBackground) { + tryTransmitMessages(); + } } Future close(Function callback) async { + log.info("Closing the websocket connection!"); if (_channel != null) { await _channel!.sink.close(); callback(); @@ -83,7 +86,7 @@ class ApiProvider { } Future connect() async { - if (_channel != null && _channel!.closeCode != null) { + if (_channel != null) { return true; } // ensure that the connect function is not called again by the timer. @@ -93,13 +96,13 @@ class ApiProvider { isAuthenticated = false; - log.info("Trying to connect to the backend $apiUrl!"); + log.fine("Trying to connect to the backend $apiUrl!"); if (await _connectTo(apiUrl)) { await onConnected(); return true; } if (backupApiUrl != null) { - log.info("Trying to connect to the backup backend $backupApiUrl!"); + log.fine("Trying to connect to the backup backend $backupApiUrl!"); if (await _connectTo(backupApiUrl!)) { await onConnected(); return true; @@ -111,6 +114,7 @@ class ApiProvider { bool get isConnected => _channel != null && _channel!.closeCode != null; void _onDone() { + log.info("WebSocket Closed"); globalCallbackConnectionState(false); _channel = null; isAuthenticated = false; @@ -118,7 +122,7 @@ class ApiProvider { } void _onError(dynamic e) { - log.shout("WebSocket Error: $e"); + log.info("WebSocket Error: $e"); globalCallbackConnectionState(false); _channel = null; isAuthenticated = false; @@ -126,6 +130,8 @@ class ApiProvider { } void tryToReconnect() { + return; + if (globalIsAppInBackground) return; if (reconnectionTimer != null) { reconnectionTimer!.cancel(); } @@ -186,6 +192,7 @@ class ApiProvider { Future _sendRequestV0(ClientToServer request, {bool authenticated = true}) async { if (_channel == null) { + log.shout("sending request, but api is not connected."); if (!await connect()) { return Result.error(ErrorCode.InternalError); } @@ -209,8 +216,13 @@ class ApiProvider { isAuthenticated = false; if (authenticated) { await authenticate(); - // this will send the request one more time. - return _sendRequestV0(request, authenticated: false); + if (isAuthenticated) { + // this will send the request one more time. + return _sendRequestV0(request, authenticated: false); + } else { + log.shout("Session is not authenticated."); + return Result.error(ErrorCode.InternalError); + } } } } diff --git a/lib/src/services/fcm_service.dart b/lib/src/services/fcm_service.dart index 1794eb4..7d8823a 100644 --- a/lib/src/services/fcm_service.dart +++ b/lib/src/services/fcm_service.dart @@ -89,7 +89,6 @@ Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { final stopwatch = Stopwatch()..start(); while (!gotMessage) { - print("gotMessage: $gotMessage"); if (stopwatch.elapsed >= Duration(seconds: 20)) { Logger("firebase-background").shout('Timeout reached. Exiting the loop.'); break; // Exit the loop if the timeout is reached. diff --git a/lib/src/utils/storage.dart b/lib/src/utils/storage.dart index 0064f83..0c4d524 100644 --- a/lib/src/utils/storage.dart +++ b/lib/src/utils/storage.dart @@ -20,7 +20,6 @@ Future getUser() async { } try { final userMap = jsonDecode(userJson) as Map; - Logger("get_user").info("Found user: $userMap"); final user = UserData.fromJson(userMap); return user; } catch (e) { diff --git a/lib/src/views/chats/chat_item_details_view.dart b/lib/src/views/chats/chat_item_details_view.dart index 0879215..5bd1255 100644 --- a/lib/src/views/chats/chat_item_details_view.dart +++ b/lib/src/views/chats/chat_item_details_view.dart @@ -144,6 +144,13 @@ class _ChatItemDetailsViewState extends State { context .read() .loadMessagesForUser(user.userId.toInt()); + initAsync(); + } + + Future initAsync() async { + context + .read() + .loadMessagesForUser(user.userId.toInt(), force: true); } Future _sendMessage() async { diff --git a/lib/src/views/chats/chat_list_view.dart b/lib/src/views/chats/chat_list_view.dart index 3dca278..dd9fe67 100644 --- a/lib/src/views/chats/chat_list_view.dart +++ b/lib/src/views/chats/chat_list_view.dart @@ -162,17 +162,31 @@ class _UserListItem extends State { @override void initState() { super.initState(); + initAsync(); lastUpdateTime(); } + Future initAsync() async { + if (widget.lastMessage != null) { + if (!widget.lastMessage!.isDownloaded) { + final content = widget.lastMessage!.messageContent; + if (content is MediaMessageContent) { + tryDownloadMedia(widget.lastMessage!.messageId, + widget.lastMessage!.otherUserId, content.downloadToken); + } + } + } + } + void lastUpdateTime() { // Change the color every 200 milliseconds updateTime = Timer.periodic(Duration(milliseconds: 200), (timer) { setState(() { - lastMessageInSeconds = - calculateTimeDifference(DateTime.now(), widget.lastMessage!.sendAt) - .inSeconds; - setState(() {}); + if (widget.lastMessage != null) { + lastMessageInSeconds = calculateTimeDifference( + DateTime.now(), widget.lastMessage!.sendAt) + .inSeconds; + } }); }); } diff --git a/lib/src/views/chats/media_viewer_view.dart b/lib/src/views/chats/media_viewer_view.dart index 670419b..53e60cb 100644 --- a/lib/src/views/chats/media_viewer_view.dart +++ b/lib/src/views/chats/media_viewer_view.dart @@ -207,12 +207,13 @@ class _MediaViewerViewState extends State { children: [ if (canBeSeenUntil != null) SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - value: progress, - strokeWidth: 2.0, - )), + width: 20, + height: 20, + child: CircularProgressIndicator( + value: progress, + strokeWidth: 2.0, + ), + ), ], ), ), diff --git a/lib/src/views/chats/search_username_view.dart b/lib/src/views/chats/search_username_view.dart index 47354dc..9898bce 100644 --- a/lib/src/views/chats/search_username_view.dart +++ b/lib/src/views/chats/search_username_view.dart @@ -50,6 +50,9 @@ class _SearchUsernameView extends State { ); } } + } else if (context.mounted) { + showAlertDialog(context, context.lang.searchUsernameNotFound, + context.lang.searchUsernameNotFoundBody(searchUserName.text)); } setState(() { _isLoading = false; diff --git a/pubspec.yaml b/pubspec.yaml index a689a89..73174be 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ description: "Send pictures to friends in real time and be sure you are the only # Prevent accidental publishing to pub.dev. publish_to: 'none' -version: 0.0.1+1 +version: 0.0.3+3 environment: sdk: ^3.5.4