From 84448f79caeac81b8be2f23a7313d07c1c8b649b Mon Sep 17 00:00:00 2001 From: otsmr Date: Sun, 9 Feb 2025 19:50:15 +0100 Subject: [PATCH] bug fixes --- lib/src/components/connection_state.dart | 68 +++++++++++++++++++ lib/src/localization/app_de.arb | 4 ++ lib/src/localization/app_en.arb | 4 ++ lib/src/model/messages_model.dart | 3 +- lib/src/providers/api/api.dart | 13 +++- lib/src/providers/api/server_messages.dart | 11 +-- lib/src/providers/api_provider.dart | 21 ++++-- lib/src/tasks/websocket_foreground_task.dart | 3 +- .../views/chats/chat_item_details_view.dart | 2 +- lib/src/views/chats/chat_list_view.dart | 2 +- lib/src/views/chats/media_viewer_view.dart | 57 +++++++++------- lib/src/views/contact/contact_view.dart | 14 ++-- 12 files changed, 155 insertions(+), 47 deletions(-) create mode 100644 lib/src/components/connection_state.dart diff --git a/lib/src/components/connection_state.dart b/lib/src/components/connection_state.dart new file mode 100644 index 0000000..cae374b --- /dev/null +++ b/lib/src/components/connection_state.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; + +class ConnectionInfo extends StatefulWidget { + const ConnectionInfo({super.key}); + + @override + State createState() => _ConnectionInfoWidgetState(); +} + +class _ConnectionInfoWidgetState extends State { + int redColorOpacity = 100; // Initial opacity value + bool redColorGoUp = true; // Direction of the opacity change + double screenWidth = 0; // To hold the screen width + + @override + void initState() { + super.initState(); + _startColorAnimation(); + } + + void _startColorAnimation() { + // Change the color every 200 milliseconds + Future.delayed(Duration(milliseconds: 200), () { + setState(() { + if (redColorOpacity <= 100) { + redColorGoUp = true; + } + if (redColorOpacity >= 150) { + redColorGoUp = false; + } + if (redColorGoUp) { + redColorOpacity += 10; + } else { + redColorOpacity -= 10; + } + }); + _startColorAnimation(); // Repeat the animation + }); + } + + @override + Widget build(BuildContext context) { + screenWidth = MediaQuery.of(context).size.width; // Get the screen width + + return Stack( + children: [ + Positioned( + top: 3, // Position it at the top + left: (screenWidth * 0.5) / 2, // Center it horizontally + child: AnimatedContainer( + duration: Duration(milliseconds: 100), + width: screenWidth * 0.5, // 50% of the screen width + decoration: BoxDecoration( + border: Border.all( + color: Colors.red[600]! + .withAlpha(redColorOpacity), // Use the animated opacity + width: 2.0, // Red border width + ), + borderRadius: BorderRadius.all( + Radius.circular(10.0), + ), // Rounded corners + ), + ), + ), + ], + ); + } +} diff --git a/lib/src/localization/app_de.arb b/lib/src/localization/app_de.arb index ba23f27..4944be3 100644 --- a/lib/src/localization/app_de.arb +++ b/lib/src/localization/app_de.arb @@ -68,6 +68,10 @@ "contactVerifyNumberMarkAsVerified": "Als verifiziert markieren", "contactVerifyNumberClearVerification": "Verifizierung aufheben", "contactVerifyNumberLongDesc": "Um die Ende-zu-Ende-Verschlüsselung mit {username} zu verifizieren, vergleiche die Zahlen mit ihrem Gerät. Die Person kann auch deinen Code mit ihrem Gerät scannen.", + "contactNickame": "Spitzname", + "contactBlock": "Blockieren", + "contactBlockTitle": "Blockiere {username}", + "contactBlockBody": "Ein blockierter Benutzer kann dir keine Nachrichten mehr senden, und sein Profil ist nicht mehr sichtbar. Um die Blockierung eines Benutzers aufzuheben, navigiere einfach zu Einstellungen > Datenschutz > Blockierte Benutzer.", "undo": "Rückgängig", "redo": "Wiederholen", "next": "Weiter", diff --git a/lib/src/localization/app_en.arb b/lib/src/localization/app_en.arb index 0192700..4f76069 100644 --- a/lib/src/localization/app_en.arb +++ b/lib/src/localization/app_en.arb @@ -68,6 +68,10 @@ "contactVerifyNumberMarkAsVerified": "Mark as verified", "contactVerifyNumberClearVerification": "Clear verification", "contactVerifyNumberLongDesc": "To verify the end-to-end encryption with {username}, compare the numbers with their device. The person can also scan your code with their device.", + "contactNickame": "Nickname", + "contactBlock": "Block", + "contactBlockTitle": "Block {username}", + "contactBlockBody": "A blocked user will no longer be able to send you messages and their profile will be hidden from view. To unblock a user, simply navigate to Settings > Privacy > Blocked Users.", "undo": "Undo", "redo": "Redo", "next": "Next", diff --git a/lib/src/model/messages_model.dart b/lib/src/model/messages_model.dart index 5ca4344..0e246eb 100644 --- a/lib/src/model/messages_model.dart +++ b/lib/src/model/messages_model.dart @@ -143,7 +143,7 @@ class DbMessages extends CvModelBase { } } - static Future deleteMessageById(int messageId) async { + static Future deleteMessageById(int messageId) async { await dbProvider.db!.delete( tableName, where: '$columnMessageId = ?', @@ -153,6 +153,7 @@ class DbMessages extends CvModelBase { if (fromUserId != null) { globalCallBackOnMessageChange(fromUserId); } + return fromUserId; } static Future getFromUserIdByMessageId(int messageId) async { diff --git a/lib/src/providers/api/api.dart b/lib/src/providers/api/api.dart index 917e08f..a576129 100644 --- a/lib/src/providers/api/api.dart +++ b/lib/src/providers/api/api.dart @@ -216,7 +216,9 @@ Future sendImage( } } -Future tryDownloadMedia(List mediaToken, {bool force = false}) async { +Future tryDownloadMedia(int messageId, List mediaToken, + {bool force = false}) async { + if (globalIsAppInBackground) return; if (!force) { // TODO: create option to enable download via mobile data final List connectivityResult = @@ -234,6 +236,7 @@ Future tryDownloadMedia(List mediaToken, {bool force = false}) async { offset = media.length; } globalCallBackOnDownloadChange(mediaToken, true); + box.put("${mediaToken}_messageId", messageId); apiProvider.triggerDownload(mediaToken, offset); } @@ -254,7 +257,13 @@ Future userOpenedOtherMessage(int fromUserId, int messageOtherId) async { Future getDownloadedMedia( List mediaToken, int messageOtherId) async { final box = await getMediaStorage(); - Uint8List? media = box.get("${mediaToken}_downloaded"); + Uint8List? media; + try { + media = box.get("${mediaToken}_downloaded"); + } catch (e) { + return null; + } + if (media == null) return null; int fromUserId = box.get("${mediaToken}_fromUserId"); await userOpenedOtherMessage(fromUserId, messageOtherId); diff --git a/lib/src/providers/api/server_messages.dart b/lib/src/providers/api/server_messages.dart index a0d6ccb..befdf16 100644 --- a/lib/src/providers/api/server_messages.dart +++ b/lib/src/providers/api/server_messages.dart @@ -59,9 +59,9 @@ Future handleDownloadData(DownloadData data) async { int? messageId = box.get("${data.uploadToken}_messageId"); if (messageId != null) { - await DbMessages.deleteMessageById(messageId); + int? fromUserId = await DbMessages.deleteMessageById(messageId); box.delete(boxId); - int? fromUserId = box.get("${data.uploadToken}_fromUserId"); + // int? fromUserId = box.get("${data.uploadToken}_fromUserId"); if (fromUserId != null) { globalCallBackOnMessageChange(fromUserId); } @@ -70,6 +70,10 @@ Future handleDownloadData(DownloadData data) async { globalCallBackOnDownloadChange(data.uploadToken, false); var ok = client.Response_Ok()..none = true; return client.Response()..ok = ok; + } else { + globalCallBackOnDownloadChange(data.uploadToken, false); + var ok = client.Response_Ok()..none = true; + return client.Response()..ok = ok; } } @@ -183,8 +187,7 @@ Future handleNewMessage( List downloadToken = content.downloadToken; Box box = await getMediaStorage(); box.put("${downloadToken}_fromUserId", fromUserId.toInt()); - box.put("${downloadToken}_messageId", messageId); - tryDownloadMedia(downloadToken); + tryDownloadMedia(messageId, downloadToken); } } localPushNotificationNewMessage( diff --git a/lib/src/providers/api_provider.dart b/lib/src/providers/api_provider.dart index 388215d..d4f1e49 100644 --- a/lib/src/providers/api_provider.dart +++ b/lib/src/providers/api_provider.dart @@ -179,20 +179,31 @@ class ApiProvider { return Result.error(ErrorCode.InternalError); } } - if (authenticated) { - await authenticate(); + if (_channel == null) { + return Result.error(ErrorCode.InternalError); } + var seq = Int64(Random().nextInt(4294967296)); while (messagesV0.containsKey(seq)) { seq = Int64(Random().nextInt(4294967296)); } + request.v0.seq = seq; - final requestBytes = request.writeToBuffer(); - _channel!.sink.add(requestBytes); - return asResult(await _waitForResponse(seq)); + Result res = asResult(await _waitForResponse(seq)); + if (res.isError) { + if (res.error == ErrorCode.SessionNotAuthenticated) { + isAuthenticated = false; + if (authenticated) { + await authenticate(); + // this will send the request one more time. + return _sendRequestV0(request, authenticated: false); + } + } + } + return res; } Future authenticate() async { diff --git a/lib/src/tasks/websocket_foreground_task.dart b/lib/src/tasks/websocket_foreground_task.dart index f366700..c1a94fb 100644 --- a/lib/src/tasks/websocket_foreground_task.dart +++ b/lib/src/tasks/websocket_foreground_task.dart @@ -41,6 +41,7 @@ class WebsocketForegroundTask extends TaskHandler { // Called when data is sent using `FlutterForegroundTask.sendDataToTask`. @override void onReceiveData(Object data) { + apiProvider.close(() {}); print('onReceiveData: $data'); } @@ -53,7 +54,7 @@ class WebsocketForegroundTask extends TaskHandler { // Called when the notification itself is pressed. @override void onNotificationPressed() { - print('onNotificationPressed'); + apiProvider.close(() {}); } // Called when the notification itself is dismissed. diff --git a/lib/src/views/chats/chat_item_details_view.dart b/lib/src/views/chats/chat_item_details_view.dart index b777ed9..d4f337c 100644 --- a/lib/src/views/chats/chat_item_details_view.dart +++ b/lib/src/views/chats/chat_item_details_view.dart @@ -79,7 +79,7 @@ class ChatListEntry extends StatelessWidget { }), ); } else { - tryDownloadMedia(token, force: true); + tryDownloadMedia(message.messageId, token, force: true); } } }, diff --git a/lib/src/views/chats/chat_list_view.dart b/lib/src/views/chats/chat_list_view.dart index 4328c7d..8e23a47 100644 --- a/lib/src/views/chats/chat_list_view.dart +++ b/lib/src/views/chats/chat_list_view.dart @@ -213,7 +213,7 @@ class _UserListItem extends State { onTap: () { if (isDownloading) return; if (!widget.lastMessage.isDownloaded) { - tryDownloadMedia(token, force: true); + tryDownloadMedia(widget.lastMessage.messageId, token, force: true); return; } if (state == MessageSendState.received && diff --git a/lib/src/views/chats/media_viewer_view.dart b/lib/src/views/chats/media_viewer_view.dart index d3788e9..5e0953e 100644 --- a/lib/src/views/chats/media_viewer_view.dart +++ b/lib/src/views/chats/media_viewer_view.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:local_auth/local_auth.dart'; import 'package:lottie/lottie.dart'; import 'package:no_screenshot/no_screenshot.dart'; @@ -74,6 +73,12 @@ class _MediaViewerViewState extends State { List token = content.downloadToken; _imageByte = await getDownloadedMedia(token, widget.message.messageOtherId!); + if (_imageByte == null) { + // image already deleted + if (context.mounted) { + Navigator.pop(context); + } + } setState(() {}); } @@ -204,31 +209,31 @@ class _MediaViewerViewState extends State { ], ), ), - if (_imageByte != null && false) - Positioned( - bottom: 30, - left: 0, - right: 0, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // const SizedBox(width: 20), - FilledButton.icon( - icon: FaIcon(FontAwesomeIcons.solidPaperPlane), - onPressed: () async {}, - style: ButtonStyle( - padding: WidgetStateProperty.all( - EdgeInsets.symmetric(vertical: 10, horizontal: 30), - ), - ), - label: Text( - "Respond", - style: TextStyle(fontSize: 17), - ), - ), - ], - ), - ), + // if (_imageByte != null) + // Positioned( + // bottom: 30, + // left: 0, + // right: 0, + // child: Row( + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // // const SizedBox(width: 20), + // FilledButton.icon( + // icon: FaIcon(FontAwesomeIcons.solidPaperPlane), + // onPressed: () async {}, + // style: ButtonStyle( + // padding: WidgetStateProperty.all( + // EdgeInsets.symmetric(vertical: 10, horizontal: 30), + // ), + // ), + // label: Text( + // "Respond", + // style: TextStyle(fontSize: 17), + // ), + // ), + // ], + // ), + // ), ], ), ), diff --git a/lib/src/views/contact/contact_view.dart b/lib/src/views/contact/contact_view.dart index bff2203..e90e3fd 100644 --- a/lib/src/views/contact/contact_view.dart +++ b/lib/src/views/contact/contact_view.dart @@ -8,6 +8,7 @@ import 'package:twonly/src/components/verified_shield.dart'; import 'package:twonly/src/model/contacts_model.dart'; import 'package:flutter/material.dart'; import 'package:twonly/src/providers/messages_change_provider.dart'; +import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/contact/contact_verify_view.dart'; class ContactView extends StatefulWidget { @@ -67,7 +68,7 @@ class _ContactViewState extends State { SizedBox(height: 50), BetterListTile( icon: FontAwesomeIcons.pencil, - text: "Nickname", + text: context.lang.contactNickame, onTap: () async { String? newUsername = await showNicknameChangeDialog(context, widget.contact); @@ -80,7 +81,7 @@ class _ContactViewState extends State { const Divider(), BetterListTile( icon: FontAwesomeIcons.shieldHeart, - text: "Verify safety number", + text: context.lang.contactVerifyNumberTitle, onTap: () { Navigator.push(context, MaterialPageRoute( builder: (context) { @@ -92,12 +93,13 @@ class _ContactViewState extends State { BetterListTile( icon: FontAwesomeIcons.ban, color: Colors.red, - text: "Block", + text: context.lang.contactBlock, onTap: () async { bool block = await showAlertDialog( - context, - "Block ${widget.contact.displayName}", - "A blocked user will no longer be able to send you messages and their profile will be hidden from view. To unblock a user, simply navigate to Settings > Privacy > Blocked Users.."); + context, + context.lang.contactBlockTitle(widget.contact.displayName), + context.lang.contactBlockBody, + ); if (block) { await DbContacts.blockUser(widget.contact.userId.toInt()); // go back to the first