From 2e7b0edce32d26ef0abed0869f8f09fb6c186610 Mon Sep 17 00:00:00 2001 From: otsmr Date: Tue, 4 Feb 2025 22:42:18 +0100 Subject: [PATCH] use improved message content --- lib/src/components/flame.dart | 4 +- .../components/image_editor/data/layer.dart | 2 + .../image_editor/layers/draw_layer.dart | 4 +- .../components/message_send_state_icon.dart | 7 +- lib/src/model/json/message.dart | 102 ++++++++++++++---- lib/src/model/json/message.g.dart | 56 ---------- lib/src/model/messages_model.dart | 35 +++--- lib/src/providers/api/api.dart | 73 ++++++++++--- lib/src/providers/api/server_messages.dart | 17 +-- .../share_image_editor_view.dart | 15 ++- .../camera_to_share/share_image_view.dart | 38 +++++-- .../views/chats/chat_item_details_view.dart | 55 +++++----- lib/src/views/chats/chat_list_view.dart | 27 ++--- lib/src/views/chats/media_viewer_view.dart | 12 ++- lib/src/views/chats/search_username_view.dart | 3 + 15 files changed, 260 insertions(+), 190 deletions(-) delete mode 100644 lib/src/model/json/message.g.dart diff --git a/lib/src/components/flame.dart b/lib/src/components/flame.dart index 62a166b..0fb470b 100644 --- a/lib/src/components/flame.dart +++ b/lib/src/components/flame.dart @@ -20,11 +20,11 @@ class FlameCounterWidget extends StatelessWidget { const SizedBox(width: 5), Text( user.flameCounter.toString(), - style: const TextStyle(fontSize: 12), + style: const TextStyle(fontSize: 13), ), Text( (maxTotalMediaCounter == user.totalMediaCounter) ? "❤️‍🔥" : "🔥", - style: const TextStyle(fontSize: 10), + style: const TextStyle(fontSize: 14), ), ], ); diff --git a/lib/src/components/image_editor/data/layer.dart b/lib/src/components/image_editor/data/layer.dart index 4c6f026..db25ac2 100755 --- a/lib/src/components/image_editor/data/layer.dart +++ b/lib/src/components/image_editor/data/layer.dart @@ -9,6 +9,7 @@ class Layer { bool isEditing; bool isDeleted; bool hasCustomActionButtons; + bool showCustomButtons; Layer({ this.offset = const Offset(0, 0), @@ -16,6 +17,7 @@ class Layer { this.isEditing = false, this.isDeleted = false, this.hasCustomActionButtons = false, + this.showCustomButtons = true, this.rotation = 0, this.scale = 1, }); diff --git a/lib/src/components/image_editor/layers/draw_layer.dart b/lib/src/components/image_editor/layers/draw_layer.dart index 6c6acc3..b81bace 100644 --- a/lib/src/components/image_editor/layers/draw_layer.dart +++ b/lib/src/components/image_editor/layers/draw_layer.dart @@ -98,7 +98,7 @@ class _DrawLayerState extends State { ), ), ), - if (widget.layerData.isEditing) + if (widget.layerData.isEditing && widget.layerData.showCustomButtons) Positioned( top: 5, left: 5, @@ -143,7 +143,7 @@ class _DrawLayerState extends State { ], ), ), - if (widget.layerData.isEditing) + if (widget.layerData.isEditing && widget.layerData.showCustomButtons) Positioned( right: 20, top: 50, diff --git a/lib/src/components/message_send_state_icon.dart b/lib/src/components/message_send_state_icon.dart index bdd491d..5fba946 100644 --- a/lib/src/components/message_send_state_icon.dart +++ b/lib/src/components/message_send_state_icon.dart @@ -70,11 +70,10 @@ class MessageSendStateIcon extends StatelessWidget { } bool isDownloading = false; - if (message.messageContent != null && - message.messageContent!.downloadToken != null) { + final content = message.messageContent; + if (message.messageReceived && content is MediaMessageContent) { final test = context.watch().currentlyDownloading; - isDownloading = - test.contains(message.messageContent!.downloadToken.toString()); + isDownloading = test.contains(content.downloadToken.toString()); } if (isDownloading) { diff --git a/lib/src/model/json/message.dart b/lib/src/model/json/message.dart index 90854f0..02aaf68 100644 --- a/lib/src/model/json/message.dart +++ b/lib/src/model/json/message.dart @@ -1,7 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:twonly/src/utils/json.dart'; -part 'message.g.dart'; enum MessageKind { textMessage, @@ -38,24 +35,17 @@ extension MessageKindExtension on MessageKind { } } -// so _$MessageKindEnumMap gets generated -@JsonSerializable() -class _MessageKind { - MessageKind? kind; -} - -@JsonSerializable() +// TODO: use message as base class, remove kind and flatten content class Message { - @Int64Converter() final MessageKind kind; - final MessageContent? content; + final MessageContent content; final int? messageId; DateTime timestamp; Message( {required this.kind, this.messageId, - this.content, + required this.content, required this.timestamp}); @override @@ -63,19 +53,85 @@ class Message { return 'Message(kind: $kind, content: $content, timestamp: $timestamp)'; } - factory Message.fromJson(Map json) => - _$MessageFromJson(json); - Map toJson() => _$MessageToJson(this); + static Message fromJson(Map json) => Message( + kind: MessageKindExtension.fromString(json["kind"]), + messageId: (json['messageId'] as num?)?.toInt(), + content: + MessageContent.fromJson(json['content'] as Map), + timestamp: DateTime.parse(json['timestamp'] as String), + ); + + Map toJson() => { + 'kind': kind.name, + 'content': content.toJson(), + 'messageId': messageId, + 'timestamp': timestamp.toIso8601String(), + }; } -@JsonSerializable() class MessageContent { - final String? text; - final List? downloadToken; + MessageContent(); - MessageContent({required this.text, required this.downloadToken}); + static MessageContent fromJson(Map json) { + switch (json['type']) { + case 'MediaMessageContent': + return MediaMessageContent.fromJson(json); + case 'TextMessageContent': + return TextMessageContent.fromJson(json); + default: + return MessageContent(); + } + } - factory MessageContent.fromJson(Map json) => - _$MessageContentFromJson(json); - Map toJson() => _$MessageContentToJson(this); + Map toJson() { + return {}; + } +} + +class MediaMessageContent extends MessageContent { + final List downloadToken; + final int maxShowTime; + final bool isRealTwonly; + MediaMessageContent({ + required this.downloadToken, + required this.maxShowTime, + required this.isRealTwonly, + }); + + static MediaMessageContent fromJson(Map json) { + return MediaMessageContent( + downloadToken: List.from(json['downloadToken']), + maxShowTime: json['maxShowTime'], + isRealTwonly: json['isRealTwonly'], + ); + } + + @override + Map toJson() { + return { + 'type': 'MediaMessageContent', + 'downloadToken': downloadToken, + 'isRealTwonly': isRealTwonly, + 'maxShowTime': maxShowTime, + }; + } +} + +class TextMessageContent extends MessageContent { + final String text; + TextMessageContent({required this.text}); + + static TextMessageContent fromJson(Map json) { + return TextMessageContent( + text: json['text'], + ); + } + + @override + Map toJson() { + return { + 'type': 'TextMessageContent', + 'text': text, + }; + } } diff --git a/lib/src/model/json/message.g.dart b/lib/src/model/json/message.g.dart deleted file mode 100644 index 71a0ae5..0000000 --- a/lib/src/model/json/message.g.dart +++ /dev/null @@ -1,56 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'message.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_MessageKind _$MessageKindFromJson(Map json) => _MessageKind() - ..kind = $enumDecodeNullable(_$MessageKindEnumMap, json['kind']); - -Map _$MessageKindToJson(_MessageKind instance) => - { - 'kind': _$MessageKindEnumMap[instance.kind], - }; - -const _$MessageKindEnumMap = { - MessageKind.textMessage: 'textMessage', - MessageKind.image: 'image', - MessageKind.video: 'video', - MessageKind.contactRequest: 'contactRequest', - MessageKind.rejectRequest: 'rejectRequest', - MessageKind.acceptRequest: 'acceptRequest', - MessageKind.opened: 'opened', - MessageKind.ack: 'ack', -}; - -Message _$MessageFromJson(Map json) => Message( - kind: $enumDecode(_$MessageKindEnumMap, json['kind']), - messageId: (json['messageId'] as num?)?.toInt(), - content: json['content'] == null - ? null - : MessageContent.fromJson(json['content'] as Map), - timestamp: DateTime.parse(json['timestamp'] as String), - ); - -Map _$MessageToJson(Message instance) => { - 'kind': _$MessageKindEnumMap[instance.kind]!, - 'content': instance.content, - 'messageId': instance.messageId, - 'timestamp': instance.timestamp.toIso8601String(), - }; - -MessageContent _$MessageContentFromJson(Map json) => - MessageContent( - text: json['text'] as String?, - downloadToken: (json['downloadToken'] as List?) - ?.map((e) => (e as num).toInt()) - .toList(), - ); - -Map _$MessageContentToJson(MessageContent instance) => - { - 'text': instance.text, - 'downloadToken': instance.downloadToken, - }; diff --git a/lib/src/model/messages_model.dart b/lib/src/model/messages_model.dart index a56c10b..2b6042e 100644 --- a/lib/src/model/messages_model.dart +++ b/lib/src/model/messages_model.dart @@ -39,6 +39,8 @@ class DbMessage { return isMedia(); } + bool get messageReceived => messageOtherId != null; + bool isMedia() { return messageKind == MessageKind.image || messageKind == MessageKind.video; } @@ -84,7 +86,7 @@ class DbMessages extends CvModelBase { final messageKind = CvField(columnMessageKind); static const columnMessageContentJson = "message_json"; - final messageContentJson = CvField(columnMessageContentJson); + final messageContentJson = CvField(columnMessageContentJson); static const columnMessageOpenedAt = "message_opened_at"; final messageOpenedAt = CvField(columnMessageOpenedAt); @@ -112,7 +114,7 @@ class DbMessages extends CvModelBase { $columnMessageKind INTEGER NOT NULL, $columnMessageAcknowledgeByUser INTEGER NOT NULL DEFAULT 0, $columnMessageAcknowledgeByServer INTEGER NOT NULL DEFAULT 0, - $columnMessageContentJson TEXT DEFAULT NULL, + $columnMessageContentJson TEXT NOT NULL, $columnMessageOpenedAt DATETIME DEFAULT NULL, $columnSendOrReceivedAt DATETIME DEFAULT CURRENT_TIMESTAMP, $columnUpdatedAt DATETIME DEFAULT CURRENT_TIMESTAMP @@ -145,12 +147,12 @@ class DbMessages extends CvModelBase { return null; } - static Future insertMyMessage(int userIdFrom, MessageKind kind, - {String? jsonContent}) async { + static Future insertMyMessage( + int userIdFrom, MessageKind kind, MessageContent content) async { try { int messageId = await dbProvider.db!.insert(tableName, { columnMessageKind: kind.index, - columnMessageContentJson: jsonContent, + columnMessageContentJson: jsonEncode(content.toJson()), columnOtherUserId: userIdFrom, columnSendOrReceivedAt: DateTime.now().toIso8601String() }); @@ -235,7 +237,9 @@ class DbMessages extends CvModelBase { List receivedByOther = messages .where((c) => c.messageOtherId != null && c.messageOpenedAt == null) .toList(); - if (receivedByOther.isNotEmpty) return receivedByOther[0]; + if (receivedByOther.isNotEmpty) { + return receivedByOther[receivedByOther.length - 1]; + } // check if there is a message which was not ack by the server List notAckByServer = @@ -343,19 +347,16 @@ class DbMessages extends CvModelBase { if (messageOpenedAt != null) { messageOpenedAt = DateTime.tryParse(fromDb[i][columnMessageOpenedAt]); } - dynamic content = fromDb[i][columnMessageContentJson]; - if (content != null) { - content = MessageContent.fromJson( - jsonDecode(fromDb[i][columnMessageContentJson])); - } + int? messageOtherId = fromDb[i][columnMessageOtherId]; + MessageContent content = MessageContent.fromJson( + jsonDecode(fromDb[i][columnMessageContentJson])); MessageKind messageKind = MessageKindExtension.fromIndex(fromDb[i][columnMessageKind]); bool isDownloaded = true; - if (messageKind == MessageKind.image || - messageKind == MessageKind.video) { - // when the media was send from the user itself the content is null - if (content != null) { - isDownloaded = await isMediaDownloaded(content.downloadToken!); + if (messageOtherId != null) { + if (content is MediaMessageContent) { + // when the media was send from the user itself the content is null + isDownloaded = await isMediaDownloaded(content.downloadToken); } } parsedUsers.add( @@ -363,7 +364,7 @@ class DbMessages extends CvModelBase { sendOrReceivedAt: DateTime.tryParse(fromDb[i][columnSendOrReceivedAt])!, messageId: fromDb[i][columnMessageId], - messageOtherId: fromDb[i][columnMessageOtherId], + messageOtherId: messageOtherId, otherUserId: fromDb[i][columnOtherUserId], messageKind: messageKind, messageContent: content, diff --git a/lib/src/providers/api/api.dart b/lib/src/providers/api/api.dart index 0f8dd20..6b5468d 100644 --- a/lib/src/providers/api/api.dart +++ b/lib/src/providers/api/api.dart @@ -30,7 +30,9 @@ Future tryTransmitMessages() async { Uint8List? bytes = box.get("retransmit-$msgId-textmessage"); if (bytes != null) { Result resp = await apiProvider.sendTextMessage( - Int64(retransmit[i].otherUserId), bytes); + Int64(retransmit[i].otherUserId), + bytes, + ); if (resp.isSuccess) { DbMessages.acknowledgeMessageByServer(msgId); @@ -42,7 +44,16 @@ Future tryTransmitMessages() async { Uint8List? encryptedMedia = await box.get("retransmit-$msgId-media"); if (encryptedMedia != null) { - uploadMediaFile(msgId, Int64(retransmit[i].otherUserId), encryptedMedia); + final content = retransmit[i].messageContent; + if (content is MediaMessageContent) { + uploadMediaFile( + msgId, + Int64(retransmit[i].otherUserId), + encryptedMedia, + content.isRealTwonly, + content.maxShowTime, + ); + } } } } @@ -75,11 +86,13 @@ Future encryptAndSendMessage(Int64 userId, Message msg) async { } Future sendTextMessage(Int64 target, String message) async { - MessageContent content = MessageContent(text: message, downloadToken: null); + MessageContent content = TextMessageContent(text: message); int? messageId = await DbMessages.insertMyMessage( - target.toInt(), MessageKind.textMessage, - jsonContent: jsonEncode(content.toJson())); + target.toInt(), + MessageKind.textMessage, + content, + ); if (messageId == null) return; Message msg = Message( @@ -94,7 +107,12 @@ Future sendTextMessage(Int64 target, String message) async { // this will send the media file and ensures retransmission when errors occur Future uploadMediaFile( - int messageId, Int64 target, Uint8List encryptedMedia) async { + int messageId, + Int64 target, + Uint8List encryptedMedia, + bool isRealTwonly, + int maxShowTime, +) async { Box box = await getMediaStorage(); if ((await box.get("retransmit-$messageId-media") == null)) { @@ -133,15 +151,31 @@ Future uploadMediaFile( Message( kind: MessageKind.image, messageId: messageId, - content: MessageContent(text: null, downloadToken: uploadToken), + content: MediaMessageContent( + downloadToken: uploadToken, + maxShowTime: maxShowTime, + isRealTwonly: isRealTwonly, + ), timestamp: DateTime.now(), ), ); } -Future encryptAndUploadMediaFile(Int64 target, Uint8List imageBytes) async { - int? messageId = - await DbMessages.insertMyMessage(target.toInt(), MessageKind.image); +Future encryptAndUploadMediaFile( + Int64 target, + Uint8List imageBytes, + bool isRealTwonly, + int maxShowTime, +) async { + int? messageId = await DbMessages.insertMyMessage( + target.toInt(), + MessageKind.image, + MediaMessageContent( + downloadToken: [], + maxShowTime: maxShowTime, + isRealTwonly: isRealTwonly, + )); + // isRealTwonly, if (messageId == null) return; Uint8List? encryptBytes = await SignalHelper.encryptBytes(imageBytes, target); @@ -151,11 +185,16 @@ Future encryptAndUploadMediaFile(Int64 target, Uint8List imageBytes) async { return; } - await uploadMediaFile(messageId, target, encryptBytes); + await uploadMediaFile( + messageId, target, encryptBytes, isRealTwonly, maxShowTime); } -Future sendImage(List userIds, Uint8List imageBytes, bool isRealTwonly, - int maxShowTime) async { +Future sendImage( + List userIds, + Uint8List imageBytes, + bool isRealTwonly, + int maxShowTime, +) async { // 1. set notifier provider Uint8List? imageBytesCompressed = await getCompressedImage(imageBytes); @@ -165,7 +204,12 @@ Future sendImage(List userIds, Uint8List imageBytes, bool isRealTwonly, } for (int i = 0; i < userIds.length; i++) { - encryptAndUploadMediaFile(userIds[i], imageBytesCompressed); + encryptAndUploadMediaFile( + userIds[i], + imageBytesCompressed, + isRealTwonly, + maxShowTime, + ); } } @@ -198,6 +242,7 @@ Future userOpenedOtherMessage(int fromUserId, int messageOtherId) async { Message( kind: MessageKind.opened, messageId: messageOtherId, + content: MessageContent(), timestamp: DateTime.now(), ), ); diff --git a/lib/src/providers/api/server_messages.dart b/lib/src/providers/api/server_messages.dart index 1e2a87c..4bc45e8 100644 --- a/lib/src/providers/api/server_messages.dart +++ b/lib/src/providers/api/server_messages.dart @@ -129,7 +129,7 @@ Future handleNewMessage( Logger("handleServerMessages") .shout("Got unknown MessageKind $message"); } else { - String content = jsonEncode(message.content!.toJson()); + String content = jsonEncode(message.content.toJson()); int? messageId = await DbMessages.insertOtherMessage( fromUserId.toInt(), message.kind, message.messageId!, content); @@ -142,6 +142,7 @@ Future handleNewMessage( Message( kind: MessageKind.ack, messageId: message.messageId!, + content: MessageContent(), timestamp: DateTime.now(), ), ); @@ -151,13 +152,13 @@ Future handleNewMessage( await DbContacts.checkAndUpdateFlames(fromUserId.toInt(), timestamp: message.timestamp); - dynamic content = message.content!; - List downloadToken = content.downloadToken; - - Box box = await getMediaStorage(); - box.put("${downloadToken}_fromUserId", fromUserId.toInt()); - - tryDownloadMedia(downloadToken); + final content = message.content; + if (content is MediaMessageContent) { + List downloadToken = content.downloadToken; + Box box = await getMediaStorage(); + box.put("${downloadToken}_fromUserId", fromUserId.toInt()); + tryDownloadMedia(downloadToken); + } } } } diff --git a/lib/src/views/camera_to_share/share_image_editor_view.dart b/lib/src/views/camera_to_share/share_image_editor_view.dart index 054e886..5dc4312 100644 --- a/lib/src/views/camera_to_share/share_image_editor_view.dart +++ b/lib/src/views/camera_to_share/share_image_editor_view.dart @@ -99,7 +99,7 @@ class _ShareImageEditorView extends State { child: ActionButton( FontAwesomeIcons.stopwatch, tooltipText: context.lang.protectAsARealTwonly, - disable: _isRealTwonly, + // disable: _isRealTwonly, onPressed: () async { if (_maxShowTime == 999999) { _maxShowTime = 4; @@ -184,7 +184,13 @@ class _ShareImageEditorView extends State { Uint8List? image; if (layers.length > 1) { + for (var x in layers) { + x.showCustomButtons = false; + } image = await screenshotController.capture(pixelRatio: pixelRatio); + for (var x in layers) { + x.showCustomButtons = true; + } } else if (layers.length == 1) { if (layers.first is BackgroundLayerData) { image = (layers.first as BackgroundLayerData).image.bytes; @@ -313,14 +319,13 @@ class _ShareImageEditorView extends State { const SizedBox(width: 20), FilledButton.icon( icon: FaIcon(FontAwesomeIcons.solidPaperPlane), - onPressed: () async { - Uint8List? imageBytes = await getMergedImage(); - if (imageBytes == null || !context.mounted) return; + onPressed: () { + Future imageBytes = getMergedImage(); Navigator.push( context, MaterialPageRoute( builder: (context) => ShareImageView( - imageBytes: imageBytes, + imageBytesFuture: imageBytes, isRealTwonly: _isRealTwonly, maxShowTime: _maxShowTime, ), diff --git a/lib/src/views/camera_to_share/share_image_view.dart b/lib/src/views/camera_to_share/share_image_view.dart index 98f444b..89ab15d 100644 --- a/lib/src/views/camera_to_share/share_image_view.dart +++ b/lib/src/views/camera_to_share/share_image_view.dart @@ -15,10 +15,10 @@ import 'package:twonly/src/views/home_view.dart'; class ShareImageView extends StatefulWidget { const ShareImageView( {super.key, - required this.imageBytes, + required this.imageBytesFuture, required this.isRealTwonly, required this.maxShowTime}); - final Uint8List imageBytes; + final Future imageBytesFuture; final bool isRealTwonly; final int maxShowTime; @@ -31,21 +31,23 @@ class _ShareImageView extends State { List _otherUsers = []; List _bestFriends = []; int maxTotalMediaCounter = 0; + Uint8List? imageBytes; final HashSet _selectedUserIds = HashSet(); final TextEditingController searchUserName = TextEditingController(); @override void initState() { super.initState(); - _loadUsers(); + _loadAsync(); } - Future _loadUsers() async { + Future _loadAsync() async { final users = await DbContacts.getActiveUsers(); setState(() { _users = users; _updateUsers(_users); }); + imageBytes = await widget.imageBytesFuture; } Future _updateUsers(List users) async { @@ -153,11 +155,23 @@ class _ShareImageView extends State { mainAxisAlignment: MainAxisAlignment.end, children: [ FilledButton.icon( - icon: FaIcon(FontAwesomeIcons.solidPaperPlane), + icon: imageBytes == null + ? SizedBox( + height: 12, + width: 12, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Theme.of(context).colorScheme.inversePrimary, + ), + ) + : FaIcon(FontAwesomeIcons.solidPaperPlane), onPressed: () async { + if (imageBytes == null || _selectedUserIds.isEmpty) { + return; + } sendImage( _selectedUserIds.toList(), - widget.imageBytes, + imageBytes!, widget.isRealTwonly, widget.maxShowTime, ); @@ -168,10 +182,14 @@ class _ShareImageView extends State { globalUpdateOfHomeViewPageIndex(0); }, style: ButtonStyle( - padding: WidgetStateProperty.all( - EdgeInsets.symmetric(vertical: 10, horizontal: 30), - ), - ), + padding: WidgetStateProperty.all( + EdgeInsets.symmetric(vertical: 10, horizontal: 30), + ), + backgroundColor: WidgetStateProperty.all( + imageBytes == null || _selectedUserIds.isEmpty + ? Theme.of(context).colorScheme.secondary + : Theme.of(context).colorScheme.primary, + )), label: Text( context.lang.shareImagedEditorSendImage, style: TextStyle(fontSize: 17), diff --git a/lib/src/views/chats/chat_item_details_view.dart b/lib/src/views/chats/chat_item_details_view.dart index 63ce37e..4df3fcd 100644 --- a/lib/src/views/chats/chat_item_details_view.dart +++ b/lib/src/views/chats/chat_item_details_view.dart @@ -25,40 +25,46 @@ class ChatListEntry extends StatelessWidget { MessageSendState state = message.getSendState(); bool isDownloading = false; - if (message.messageContent != null && - message.messageContent!.downloadToken != null) { + List token = []; + + final content = message.messageContent; + if (message.messageReceived && content is MediaMessageContent) { + token = content.downloadToken; isDownloading = context .watch() .currentlyDownloading - .contains(message.messageContent!.downloadToken!.toString()); + .contains(token.toString()); } Widget child = Container(); switch (message.messageKind) { case MessageKind.textMessage: - child = Container( - constraints: BoxConstraints( - 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 - ? 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( - message.messageContent!.text!, - style: TextStyle( - color: Colors.white, // Set text color for contrast - fontSize: 17, + if (content is TextMessageContent) { + child = Container( + constraints: BoxConstraints( + maxWidth: MediaQuery.of(context).size.width * 0.8, ), - textAlign: TextAlign.left, // Center the text - ), - ); + padding: EdgeInsets.symmetric( + vertical: 4, + horizontal: 10), // Add some padding around the text + decoration: BoxDecoration( + color: right + ? 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( + content.text, + style: TextStyle( + color: Colors.white, // Set text color for contrast + fontSize: 17, + ), + textAlign: TextAlign.left, // Center the text + ), + ); + } break; case MessageKind.image: Color color = @@ -74,7 +80,6 @@ class ChatListEntry extends StatelessWidget { }), ); } else { - List token = message.messageContent!.downloadToken!; tryDownloadMedia(token, force: true); } } diff --git a/lib/src/views/chats/chat_list_view.dart b/lib/src/views/chats/chat_list_view.dart index 862d266..043b539 100644 --- a/lib/src/views/chats/chat_list_view.dart +++ b/lib/src/views/chats/chat_list_view.dart @@ -6,6 +6,7 @@ import 'package:twonly/src/components/message_send_state_icon.dart'; import 'package:twonly/src/components/notification_badge.dart'; import 'package:twonly/src/components/user_context_menu.dart'; import 'package:twonly/src/model/contacts_model.dart'; +import 'package:twonly/src/model/json/message.dart'; import 'package:twonly/src/model/messages_model.dart'; import 'package:twonly/src/providers/api/api.dart'; import 'package:twonly/src/providers/contacts_change_provider.dart'; @@ -19,20 +20,6 @@ import 'package:twonly/src/views/profile_view.dart'; import 'package:twonly/src/views/chats/search_username_view.dart'; import 'package:flutter/material.dart'; -class ChatItem { - const ChatItem( - {required this.username, - required this.flames, - required this.userId, - required this.state, - required this.lastMessageInSeconds}); - final String username; - final int lastMessageInSeconds; - final int flames; - final int userId; - final MessageSendState state; -} - /// Displays a list of SampleItems. class ChatListView extends StatefulWidget { const ChatListView({super.key}); @@ -169,7 +156,6 @@ class UserListItem extends StatefulWidget { } class _UserListItem extends State { - int flames = 0; int lastMessageInSeconds = 0; @override @@ -186,13 +172,15 @@ class _UserListItem extends State { MessageSendState state = widget.lastMessage.getSendState(); bool isDownloading = false; - if (widget.lastMessage.messageContent != null && - widget.lastMessage.messageContent!.downloadToken != null) { + final content = widget.lastMessage.messageContent; + List token = []; + + if (widget.lastMessage.messageReceived && content is MediaMessageContent) { + token = content.downloadToken; isDownloading = context .watch() .currentlyDownloading - .contains( - widget.lastMessage.messageContent!.downloadToken!.toString()); + .contains(token.toString()); } return UserContextMenu( @@ -216,7 +204,6 @@ class _UserListItem extends State { onTap: () { if (isDownloading) return; if (!widget.lastMessage.isDownloaded) { - List token = widget.lastMessage.messageContent!.downloadToken!; tryDownloadMedia(token, force: true); return; } diff --git a/lib/src/views/chats/media_viewer_view.dart b/lib/src/views/chats/media_viewer_view.dart index 331bd66..5875ea9 100644 --- a/lib/src/views/chats/media_viewer_view.dart +++ b/lib/src/views/chats/media_viewer_view.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:twonly/src/components/media_view_sizing.dart'; import 'package:twonly/src/model/contacts_model.dart'; +import 'package:twonly/src/model/json/message.dart'; import 'package:twonly/src/model/messages_model.dart'; import 'package:twonly/src/providers/api/api.dart'; @@ -25,10 +26,13 @@ class _MediaViewerViewState extends State { } Future _initAsync() async { - List token = widget.message.messageContent!.downloadToken!; - _imageByte = - await getDownloadedMedia(token, widget.message.messageOtherId!); - setState(() {}); + final content = widget.message.messageContent; + if (content is MediaMessageContent) { + List token = content.downloadToken; + _imageByte = + await getDownloadedMedia(token, widget.message.messageOtherId!); + setState(() {}); + } } @override diff --git a/lib/src/views/chats/search_username_view.dart b/lib/src/views/chats/search_username_view.dart index d1b4a31..8edecb2 100644 --- a/lib/src/views/chats/search_username_view.dart +++ b/lib/src/views/chats/search_username_view.dart @@ -45,6 +45,7 @@ class _SearchUsernameView extends State { Message( kind: MessageKind.contactRequest, timestamp: DateTime.now(), + content: MessageContent(), ), ); } @@ -179,6 +180,7 @@ class _ContactsListViewState extends State { Message( kind: MessageKind.rejectRequest, timestamp: DateTime.now(), + content: MessageContent(), ), ); }, @@ -193,6 +195,7 @@ class _ContactsListViewState extends State { Message( kind: MessageKind.acceptRequest, timestamp: DateTime.now(), + content: MessageContent(), ), ); },