From 59146674b610893375bd4a5fc568b9121cbe6040 Mon Sep 17 00:00:00 2001 From: otsmr Date: Thu, 30 Jan 2025 22:25:37 +0100 Subject: [PATCH] sending an receiving works --- lib/main.dart | 2 + lib/src/app.dart | 20 ++++-- .../components/message_send_state_icon.dart | 61 +++++++++++------ lib/src/model/messages_model.dart | 18 ++++- lib/src/proto/api/client_to_server.pb.dart | 14 ++++ .../proto/api/client_to_server.pbjson.dart | 5 +- lib/src/proto/api/error.pbenum.dart | 2 + lib/src/proto/api/error.pbjson.dart | 4 +- lib/src/providers/api/api.dart | 32 +++++---- lib/src/providers/api/server_messages.dart | 25 ++++--- lib/src/providers/api_provider.dart | 7 +- .../providers/download_change_provider.dart | 16 +++++ .../providers/messages_change_provider.dart | 7 ++ lib/src/views/chat_item_details_view.dart | 67 +++++++++++++------ lib/src/views/chat_list_view.dart | 17 +++-- lib/src/views/media_viewer_view.dart | 3 +- 16 files changed, 217 insertions(+), 83 deletions(-) create mode 100644 lib/src/providers/download_change_provider.dart diff --git a/lib/main.dart b/lib/main.dart index c011619..b148cab 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,6 +7,7 @@ import 'package:twonly/src/providers/api_provider.dart'; import 'package:twonly/src/providers/db_provider.dart'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; +import 'package:twonly/src/providers/download_change_provider.dart'; import 'package:twonly/src/providers/messages_change_provider.dart'; import 'package:twonly/src/providers/contacts_change_provider.dart'; import 'package:twonly/src/utils/misc.dart'; @@ -61,6 +62,7 @@ void main() async { MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => MessagesChangeProvider()), + ChangeNotifierProvider(create: (_) => DownloadChangeProvider()), ChangeNotifierProvider(create: (_) => ContactChangeProvider()), ], child: MyApp(settingsController: settingsController), diff --git a/lib/src/app.dart b/lib/src/app.dart index 9cd0949..6528509 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -1,6 +1,7 @@ import 'package:provider/provider.dart'; import 'package:twonly/main.dart'; import 'package:twonly/src/providers/contacts_change_provider.dart'; +import 'package:twonly/src/providers/download_change_provider.dart'; import 'package:twonly/src/providers/messages_change_provider.dart'; import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/views/onboarding_view.dart'; @@ -22,6 +23,7 @@ Function(bool) globalCallbackConnectionState = (a) {}; // these two callbacks are called on updated to the corresponding database Function globalCallBackOnContactChange = () {}; Function(int) globalCallBackOnMessageChange = (a) {}; +Function(List, bool) globalCallBackOnDownloadChange = (a, b) {}; /// The Widget that configures your application. class MyApp extends StatefulWidget { @@ -61,6 +63,10 @@ class _MyAppState extends State { context.read().update(); }; + globalCallBackOnDownloadChange = (token, add) { + context.read().update(token, add); + }; + globalCallBackOnMessageChange = (userId) { context.read().updateLastMessageFor(userId); }; @@ -73,6 +79,7 @@ class _MyAppState extends State { void dispose() { // disable globalCallbacks to the flutter tree globalCallbackConnectionState = (a) {}; + globalCallBackOnDownloadChange = (a, b) {}; globalCallBackOnContactChange = () {}; globalCallBackOnMessageChange = (a) {}; super.dispose(); @@ -143,7 +150,8 @@ class _MyAppState extends State { if (snapshot.hasData) { return snapshot.data! ? HomeView( - settingsController: widget.settingsController) + settingsController: widget.settingsController, + ) : _showOnboarding ? OnboardingView( callbackOnSuccess: () { @@ -152,10 +160,12 @@ class _MyAppState extends State { }); }, ) - : RegisterView(callbackOnSuccess: () { - _isUserCreated = isUserCreated(); - setState(() {}); - }); + : RegisterView( + callbackOnSuccess: () { + _isUserCreated = isUserCreated(); + setState(() {}); + }, + ); } else { return Container(); } diff --git a/lib/src/components/message_send_state_icon.dart b/lib/src/components/message_send_state_icon.dart index aba2934..1441127 100644 --- a/lib/src/components/message_send_state_icon.dart +++ b/lib/src/components/message_send_state_icon.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:provider/provider.dart'; import 'package:twonly/src/model/json/message.dart'; +import 'package:twonly/src/model/messages_model.dart'; +import 'package:twonly/src/providers/download_change_provider.dart'; enum MessageSendState { received, @@ -12,21 +15,32 @@ enum MessageSendState { } class MessageSendStateIcon extends StatelessWidget { - final MessageSendState state; - final MessageKind kind; - final bool isDownloaded; + final DbMessage message; + final MainAxisAlignment mainAxisAlignment; - const MessageSendStateIcon(this.state, this.isDownloaded, this.kind, - {super.key}); + const MessageSendStateIcon(this.message, + {super.key, this.mainAxisAlignment = MainAxisAlignment.end}); @override Widget build(BuildContext context) { Widget icon = Placeholder(); String text = ""; - Color color = kind.getColor(Theme.of(context).colorScheme.primary); + Color color = + message.messageKind.getColor(Theme.of(context).colorScheme.primary); - switch (state) { + Widget loaderIcon = Row( + children: [ + SizedBox( + width: 10, + height: 10, + child: CircularProgressIndicator(strokeWidth: 1, color: color), + ), + SizedBox(width: 2), + ], + ); + + switch (message.getSendState()) { case MessageSendState.receivedOpened: icon = Icon(Icons.crop_square, size: 14, color: color); text = "Received"; @@ -45,29 +59,38 @@ class MessageSendStateIcon extends StatelessWidget { break; case MessageSendState.sending: case MessageSendState.receiving: - icon = Row( - children: [ - SizedBox( - width: 10, - height: 10, - child: CircularProgressIndicator(strokeWidth: 1, color: color), - ), - SizedBox(width: 2), - ], - ); + icon = loaderIcon; text = "Sending"; break; } - if (!isDownloaded) { + if (!message.isDownloaded) { text = "Tap do load"; } + bool isDownloading = false; + if (message.messageContent != null && + message.messageContent!.downloadToken != null) { + isDownloading = context + .watch() + .currentlyDownloading + .contains(message.messageContent!.downloadToken!); + } + + if (isDownloading) { + text = "Downloading"; + icon = loaderIcon; + } + return Row( + mainAxisAlignment: mainAxisAlignment, children: [ icon, const SizedBox(width: 3), - Text(text, style: TextStyle(fontSize: 12)), + Text( + text, + style: TextStyle(fontSize: 12), + ), const SizedBox(width: 5), ], ); diff --git a/lib/src/model/messages_model.dart b/lib/src/model/messages_model.dart index 747fa7f..47cbf96 100644 --- a/lib/src/model/messages_model.dart +++ b/lib/src/model/messages_model.dart @@ -238,6 +238,17 @@ class DbMessages extends CvModelBase { } } + static Future _updateByOtherMessageId( + int fromUserId, int messageId, Map data) async { + await dbProvider.db!.update( + tableName, + data, + where: "$columnMessageOtherId = ?", + whereArgs: [messageId], + ); + globalCallBackOnMessageChange(fromUserId); + } + // this ensures that the message id can be spoofed by another person static Future _updateByMessageIdOther( int fromUserId, int messageId, Map data) async { @@ -250,14 +261,15 @@ class DbMessages extends CvModelBase { globalCallBackOnMessageChange(fromUserId); } - static Future userOpenedMessage(int messageId) async { + static Future userOpenedOtherMessage( + int otherMessageId, int fromUserId) async { Map data = { columnMessageOpenedAt: DateTime.now().toIso8601String(), }; - await _updateByMessageId(messageId, data); + await _updateByOtherMessageId(fromUserId, otherMessageId, data); } - static Future userOpenedMessageOtherUser( + static Future otherUserOpenedMyMessage( int fromUserId, int messageId, DateTime openedAt) async { Map data = { columnMessageOpenedAt: openedAt.toIso8601String(), diff --git a/lib/src/proto/api/client_to_server.pb.dart b/lib/src/proto/api/client_to_server.pb.dart index 0ff58a6..d32219a 100644 --- a/lib/src/proto/api/client_to_server.pb.dart +++ b/lib/src/proto/api/client_to_server.pb.dart @@ -854,11 +854,15 @@ class ApplicationData_UploadData extends $pb.GeneratedMessage { class ApplicationData_DownloadData extends $pb.GeneratedMessage { factory ApplicationData_DownloadData({ $core.List<$core.int>? uploadToken, + $core.int? offset, }) { final $result = create(); if (uploadToken != null) { $result.uploadToken = uploadToken; } + if (offset != null) { + $result.offset = offset; + } return $result; } ApplicationData_DownloadData._() : super(); @@ -867,6 +871,7 @@ class ApplicationData_DownloadData extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ApplicationData.DownloadData', package: const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'), createEmptyInstance: create) ..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'uploadToken', $pb.PbFieldType.OY) + ..a<$core.int>(2, _omitFieldNames ? '' : 'offset', $pb.PbFieldType.OU3) ..hasRequiredFields = false ; @@ -899,6 +904,15 @@ class ApplicationData_DownloadData extends $pb.GeneratedMessage { $core.bool hasUploadToken() => $_has(0); @$pb.TagNumber(1) void clearUploadToken() => clearField(1); + + @$pb.TagNumber(2) + $core.int get offset => $_getIZ(1); + @$pb.TagNumber(2) + set offset($core.int v) { $_setUnsignedInt32(1, v); } + @$pb.TagNumber(2) + $core.bool hasOffset() => $_has(1); + @$pb.TagNumber(2) + void clearOffset() => clearField(2); } enum ApplicationData_ApplicationData { diff --git a/lib/src/proto/api/client_to_server.pbjson.dart b/lib/src/proto/api/client_to_server.pbjson.dart index d4ef740..8c912fc 100644 --- a/lib/src/proto/api/client_to_server.pbjson.dart +++ b/lib/src/proto/api/client_to_server.pbjson.dart @@ -182,6 +182,7 @@ const ApplicationData_DownloadData$json = { '1': 'DownloadData', '2': [ {'1': 'upload_token', '3': 1, '4': 1, '5': 12, '10': 'uploadToken'}, + {'1': 'offset', '3': 2, '4': 1, '5': 13, '10': 'offset'}, ], }; @@ -204,8 +205,8 @@ final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode( 'cl9pZBgBIAEoA1IGdXNlcklkGi0KEkdldFByZWtleXNCeVVzZXJJZBIXCgd1c2VyX2lkGAEgAS' 'gDUgZ1c2VySWQaEAoOR2V0VXBsb2FkVG9rZW4aWwoKVXBsb2FkRGF0YRIhCgx1cGxvYWRfdG9r' 'ZW4YASABKAxSC3VwbG9hZFRva2VuEhYKBm9mZnNldBgCIAEoDVIGb2Zmc2V0EhIKBGRhdGEYAy' - 'ABKAxSBGRhdGEaMQoMRG93bmxvYWREYXRhEiEKDHVwbG9hZF90b2tlbhgBIAEoDFILdXBsb2Fk' - 'VG9rZW5CEQoPQXBwbGljYXRpb25EYXRh'); + 'ABKAxSBGRhdGEaSQoMRG93bmxvYWREYXRhEiEKDHVwbG9hZF90b2tlbhgBIAEoDFILdXBsb2Fk' + 'VG9rZW4SFgoGb2Zmc2V0GAIgASgNUgZvZmZzZXRCEQoPQXBwbGljYXRpb25EYXRh'); @$core.Deprecated('Use responseDescriptor instead') const Response$json = { diff --git a/lib/src/proto/api/error.pbenum.dart b/lib/src/proto/api/error.pbenum.dart index b2bd916..2bb60e5 100644 --- a/lib/src/proto/api/error.pbenum.dart +++ b/lib/src/proto/api/error.pbenum.dart @@ -29,6 +29,7 @@ class ErrorCode extends $pb.ProtobufEnum { static const ErrorCode OnlyOneSessionAllowed = ErrorCode._(1010, _omitEnumNames ? '' : 'OnlyOneSessionAllowed'); static const ErrorCode UploadLimitReached = ErrorCode._(1011, _omitEnumNames ? '' : 'UploadLimitReached'); static const ErrorCode InvalidUpdateToken = ErrorCode._(1012, _omitEnumNames ? '' : 'InvalidUpdateToken'); + static const ErrorCode InvalidOffset = ErrorCode._(1013, _omitEnumNames ? '' : 'InvalidOffset'); static const $core.List values = [ Unknown, @@ -46,6 +47,7 @@ class ErrorCode extends $pb.ProtobufEnum { OnlyOneSessionAllowed, UploadLimitReached, InvalidUpdateToken, + InvalidOffset, ]; static final $core.Map<$core.int, ErrorCode> _byValue = $pb.ProtobufEnum.initByValue(values); diff --git a/lib/src/proto/api/error.pbjson.dart b/lib/src/proto/api/error.pbjson.dart index 5a49388..7b1e994 100644 --- a/lib/src/proto/api/error.pbjson.dart +++ b/lib/src/proto/api/error.pbjson.dart @@ -32,6 +32,7 @@ const ErrorCode$json = { {'1': 'OnlyOneSessionAllowed', '2': 1010}, {'1': 'UploadLimitReached', '2': 1011}, {'1': 'InvalidUpdateToken', '2': 1012}, + {'1': 'InvalidOffset', '2': 1013}, ], }; @@ -43,5 +44,6 @@ final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode( 'VzZXJuYW1lTm90Rm91bmQQ7QcSFQoQVXNlcm5hbWVOb3RWYWxpZBDuBxIVChBJbnZhbGlkUHVi' 'bGljS2V5EO8HEiAKG1Nlc3Npb25BbHJlYWR5QXV0aGVudGljYXRlZBDwBxIcChdTZXNzaW9uTm' '90QXV0aGVudGljYXRlZBDxBxIaChVPbmx5T25lU2Vzc2lvbkFsbG93ZWQQ8gcSFwoSVXBsb2Fk' - 'TGltaXRSZWFjaGVkEPMHEhcKEkludmFsaWRVcGRhdGVUb2tlbhD0Bw=='); + 'TGltaXRSZWFjaGVkEPMHEhcKEkludmFsaWRVcGRhdGVUb2tlbhD0BxISCg1JbnZhbGlkT2Zmc2' + 'V0EPUH'); diff --git a/lib/src/providers/api/api.dart b/lib/src/providers/api/api.dart index c60a437..41a1dd7 100644 --- a/lib/src/providers/api/api.dart +++ b/lib/src/providers/api/api.dart @@ -7,6 +7,7 @@ import 'package:hive/hive.dart'; import 'package:logging/logging.dart'; import 'package:path_provider/path_provider.dart'; import 'package:twonly/main.dart'; +import 'package:twonly/src/app.dart'; import 'package:twonly/src/model/json/message.dart'; import 'package:twonly/src/model/messages_model.dart'; import 'package:twonly/src/proto/api/error.pb.dart'; @@ -56,7 +57,7 @@ Future sendTextMessage(Int64 target, String message) async { timestamp: DateTime.now(), ); - await encryptAndSendMessage(target, msg); + encryptAndSendMessage(target, msg); } Future sendImageToSingleTarget(Int64 target, Uint8List imageBytes) async { @@ -122,7 +123,7 @@ Future sendImage(List userIds, String imagePath) async { } } -Future tryDownloadMedia(List imageToken, {bool force = false}) async { +Future tryDownloadMedia(List mediaToken, {bool force = false}) async { if (!force) { // TODO: create option to enable download via mobile data final List connectivityResult = @@ -132,32 +133,39 @@ Future tryDownloadMedia(List imageToken, {bool force = false}) async { return; } } - Logger("tryDownloadMedia").info("Downloading: $imageToken"); - apiProvider.triggerDownload(imageToken); + Logger("tryDownloadMedia").info("Downloading: $mediaToken"); + int offset = 0; + final box = await getMediaStorage(); + Uint8List? media = box.get("$mediaToken"); + if (media != null && media.isNotEmpty) { + offset = media.length; + } + globalCallBackOnDownloadChange(mediaToken, true); + apiProvider.triggerDownload(mediaToken, offset); } -Future userOpenedMessage(int fromUserId, int messageId) async { - await DbMessages.userOpenedMessage(messageId); +Future userOpenedOtherMessage(int fromUserId, int messageOtherId) async { + await DbMessages.userOpenedOtherMessage(messageOtherId, fromUserId); encryptAndSendMessage( Int64(fromUserId), Message( kind: MessageKind.opened, - messageId: messageId, + messageId: messageOtherId, timestamp: DateTime.now(), ), ); } Future getDownloadedMedia( - List mediaToken, int messageId) async { + List mediaToken, int messageOtherId) async { final box = await getMediaStorage(); Uint8List? media = box.get("${mediaToken}_downloaded"); int fromUserId = box.get("${mediaToken}_fromUserId"); - await userOpenedMessage(fromUserId, messageId); - box.delete(mediaToken.toString()); - box.put("${mediaToken}_downloaded", "deleted"); - box.delete("${mediaToken}_fromUserId"); + await userOpenedOtherMessage(fromUserId, messageOtherId); + // box.delete(mediaToken.toString()); + // box.put("${mediaToken}_downloaded", "deleted"); + // box.delete("${mediaToken}_fromUserId"); return media; } diff --git a/lib/src/providers/api/server_messages.dart b/lib/src/providers/api/server_messages.dart index f411cd9..8f74c1d 100644 --- a/lib/src/providers/api/server_messages.dart +++ b/lib/src/providers/api/server_messages.dart @@ -57,7 +57,8 @@ Future handleDownloadData(DownloadData data) async { Uint8List downloadedBytes; if (buffered != null) { if (data.offset != buffered.length) { - return client.Response()..error = ErrorCode.BadRequest; + // Logger("handleDownloadData").error(object) + return client.Response()..error = ErrorCode.InvalidOffset; } var b = BytesBuilder(); b.add(buffered); @@ -70,16 +71,20 @@ Future handleDownloadData(DownloadData data) async { if (data.fin) { SignalHelper.getSignalStore(); - int fromUserId = box.get("${data.uploadToken}_fromUserId")!; - Uint8List? rawBytes = - await SignalHelper.decryptBytes(downloadedBytes, Int64(fromUserId)); + int? fromUserId = box.get("${data.uploadToken}_fromUserId"); + if (fromUserId != null) { + print(fromUserId); + Uint8List? rawBytes = + await SignalHelper.decryptBytes(downloadedBytes, Int64(fromUserId)); - if (rawBytes != null) { - box.put("${data.uploadToken}_downloaded", rawBytes); + if (rawBytes != null) { + box.put("${data.uploadToken}_downloaded", rawBytes); + } + + box.delete(boxId); + globalCallBackOnMessageChange(fromUserId); + globalCallBackOnDownloadChange(data.uploadToken, false); } - - box.delete(boxId); - globalCallBackOnMessageChange(fromUserId); } else { box.put(boxId, downloadedBytes); } @@ -102,7 +107,7 @@ Future handleNewMessage( } break; case MessageKind.opened: - await DbMessages.userOpenedMessageOtherUser( + await DbMessages.otherUserOpenedMyMessage( fromUserId.toInt(), message.messageId!, message.timestamp, diff --git a/lib/src/providers/api_provider.dart b/lib/src/providers/api_provider.dart index e1e16e9..45a8743 100644 --- a/lib/src/providers/api_provider.dart +++ b/lib/src/providers/api_provider.dart @@ -255,8 +255,11 @@ class ApiProvider { return await _sendRequestV0(req); } - Future triggerDownload(List token) async { - var get = ApplicationData_DownloadData()..uploadToken = token; + Future triggerDownload(List token, int offset) async { + log.info("Offset: ${offset}"); + var get = ApplicationData_DownloadData() + ..uploadToken = token + ..offset = offset; var appData = ApplicationData()..downloaddata = get; var req = createClientToServerFromApplicationData(appData); return await _sendRequestV0(req); diff --git a/lib/src/providers/download_change_provider.dart b/lib/src/providers/download_change_provider.dart new file mode 100644 index 0000000..5f3fa20 --- /dev/null +++ b/lib/src/providers/download_change_provider.dart @@ -0,0 +1,16 @@ +import 'dart:collection'; +import 'package:flutter/foundation.dart'; + +class DownloadChangeProvider with ChangeNotifier, DiagnosticableTreeMixin { + final HashSet> _currentlyDownloading = HashSet>(); + + HashSet> get currentlyDownloading => _currentlyDownloading; + + void update(List token, bool add) { + if (add) { + _currentlyDownloading.add(token); + } else { + _currentlyDownloading.remove(token); + } + } +} diff --git a/lib/src/providers/messages_change_provider.dart b/lib/src/providers/messages_change_provider.dart index 838e089..a9f3307 100644 --- a/lib/src/providers/messages_change_provider.dart +++ b/lib/src/providers/messages_change_provider.dart @@ -6,7 +6,10 @@ import 'package:twonly/src/model/messages_model.dart'; /// for every contact. class MessagesChangeProvider with ChangeNotifier, DiagnosticableTreeMixin { final Map _lastMessage = {}; + final Map _changeCounter = {}; + Map get lastMessage => _lastMessage; + Map get changeCounter => _changeCounter; void updateLastMessageFor(int targetUserId) async { DbMessage? last = @@ -14,6 +17,10 @@ class MessagesChangeProvider with ChangeNotifier, DiagnosticableTreeMixin { if (last != null) { _lastMessage[last.otherUserId] = last; } + if (!changeCounter.containsKey(targetUserId)) { + changeCounter[targetUserId] = 0; + } + changeCounter[targetUserId] = changeCounter[targetUserId]! + 1; } void init() async { diff --git a/lib/src/views/chat_item_details_view.dart b/lib/src/views/chat_item_details_view.dart index 5eaf53d..9919d51 100644 --- a/lib/src/views/chat_item_details_view.dart +++ b/lib/src/views/chat_item_details_view.dart @@ -1,4 +1,5 @@ -import 'package:cv/cv.dart'; +import 'dart:collection'; + import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:provider/provider.dart'; @@ -17,10 +18,10 @@ class ChatListEntry extends StatelessWidget { final DbMessage message; final Contact user; final bool lastMessageFromSameUser; + @override Widget build(BuildContext context) { bool right = message.messageOtherId == null; - MessageSendState state = message.getSendState(); Widget child = Container(); @@ -29,15 +30,15 @@ class ChatListEntry extends StatelessWidget { case MessageKind.textMessage: child = Container( constraints: BoxConstraints( - maxWidth: MediaQuery.of(context).size.width * - 0.8, // Maximum 80% of the screen width + maxWidth: MediaQuery.of(context).size.width * 0.8, ), padding: EdgeInsets.symmetric( vertical: 4, horizontal: 10), // Add some padding around the text decoration: BoxDecoration( color: right - ? Colors.deepPurpleAccent - : Colors.blueAccent, // Set the background color + ? const Color.fromARGB(107, 124, 77, 255) + : const Color.fromARGB( + 83, 68, 137, 255), // Set the background color borderRadius: BorderRadius.circular(12.0), // Set border radius ), child: Text( @@ -71,7 +72,7 @@ class ChatListEntry extends StatelessWidget { }, child: Container( padding: EdgeInsets.all(10), - width: 200, + width: 150, decoration: BoxDecoration( border: Border.all( color: color, // Set the background color @@ -79,10 +80,13 @@ class ChatListEntry extends StatelessWidget { ), borderRadius: BorderRadius.circular(12.0), // Set border radius ), - child: MessageSendStateIcon( - state, - message.isDownloaded, - message.messageKind, + child: Align( + alignment: Alignment.centerRight, + child: MessageSendStateIcon( + message, + mainAxisAlignment: + right ? MainAxisAlignment.center : MainAxisAlignment.center, + ), ), ), ); @@ -94,7 +98,7 @@ class ChatListEntry extends StatelessWidget { child: Padding( padding: lastMessageFromSameUser ? EdgeInsets.only(top: 5, bottom: 0, right: 10, left: 10) - : EdgeInsets.all(10), + : EdgeInsets.only(top: 5, bottom: 20, right: 10, left: 10), child: child), ); } @@ -112,34 +116,50 @@ class ChatItemDetailsView extends StatefulWidget { class _ChatItemDetailsViewState extends State { List _messages = []; + int lastChangeCounter = 0; final TextEditingController newMessageController = TextEditingController(); + HashSet alreadyReportedOpened = HashSet(); @override void initState() { super.initState(); - _loadAsync(); + _loadAsync(updateOpenStatus: true); } - Future _loadAsync() async { + Future _loadAsync({bool updateOpenStatus = false}) async { _messages = await DbMessages.getAllMessagesForUser(widget.user.userId.toInt()); + setState(() {}); + + if (updateOpenStatus) { + _messages.where((x) => x.messageOpenedAt == null).forEach((message) { + if (message.messageOtherId != null && + message.messageKind == MessageKind.textMessage) { + if (!alreadyReportedOpened.contains(message.messageOtherId!)) { + userOpenedOtherMessage( + message.otherUserId, message.messageOtherId!); + alreadyReportedOpened.add(message.messageOtherId!); + } + } + }); + } } Future _sendMessage() async { String text = newMessageController.text; if (text == "") return; - sendTextMessage(widget.user.userId, newMessageController.text); + await sendTextMessage(widget.user.userId, newMessageController.text); + _loadAsync(); newMessageController.clear(); } @override Widget build(BuildContext context) { - final messages = context.watch().lastMessage; - if (messages.containsKey(widget.user.userId.toInt()) && - _messages.isNotEmpty) { - final lastMessage = messages[widget.user.userId.toInt()]; - if (lastMessage!.messageId != _messages[0].messageId) { - _loadAsync(); + final changeCounter = context.watch().changeCounter; + if (changeCounter.containsKey(widget.user.userId.toInt())) { + if (changeCounter[widget.user.userId.toInt()] != lastChangeCounter) { + _loadAsync(updateOpenStatus: true); + lastChangeCounter = changeCounter[widget.user.userId.toInt()]!; } } // messages = messages.reversed.toList(); @@ -163,7 +183,10 @@ class _ChatItemDetailsViewState extends State { _messages[i].messageOtherId != null); } return ChatListEntry( - _messages[i], widget.user, lastMessageFromSameUser); + _messages[i], + widget.user, + lastMessageFromSameUser, + ); }, ), ), diff --git a/lib/src/views/chat_list_view.dart b/lib/src/views/chat_list_view.dart index ed81dec..d114ce2 100644 --- a/lib/src/views/chat_list_view.dart +++ b/lib/src/views/chat_list_view.dart @@ -156,8 +156,7 @@ class _UserListItem extends State { title: Text(widget.user.displayName), subtitle: Row( children: [ - MessageSendStateIcon(state, widget.lastMessage.isDownloaded, - widget.lastMessage.messageKind), + MessageSendStateIcon(widget.lastMessage), Text("•"), const SizedBox(width: 5), Text( @@ -190,13 +189,19 @@ class _UserListItem extends State { tryDownloadMedia(token, force: true); return; } + if (state == MessageSendState.received && + widget.lastMessage.containsOtherMedia()) { + Navigator.push( + context, + MaterialPageRoute(builder: (context) { + return MediaViewerView(widget.user, widget.lastMessage); + }), + ); + return; + } Navigator.push( context, MaterialPageRoute(builder: (context) { - if (state == MessageSendState.received && - widget.lastMessage.containsOtherMedia()) { - return MediaViewerView(widget.user, widget.lastMessage); - } return ChatItemDetailsView(user: widget.user); }), ); diff --git a/lib/src/views/media_viewer_view.dart b/lib/src/views/media_viewer_view.dart index a0edabf..0a376a3 100644 --- a/lib/src/views/media_viewer_view.dart +++ b/lib/src/views/media_viewer_view.dart @@ -27,7 +27,8 @@ class _MediaViewerViewState extends State { Future _initAsync() async { List token = widget.message.messageContent!.downloadToken!; - _imageByte = await getDownloadedMedia(token, widget.message.messageId); + _imageByte = + await getDownloadedMedia(token, widget.message.messageOtherId!); setState(() {}); }