diff --git a/lib/src/views/chats/chat_messages_components/chat_list_entry.dart b/lib/src/views/chats/chat_messages_components/chat_list_entry.dart deleted file mode 100644 index 5e9882b..0000000 --- a/lib/src/views/chats/chat_messages_components/chat_list_entry.dart +++ /dev/null @@ -1,100 +0,0 @@ -import 'dart:convert'; -import 'package:flutter/material.dart'; -import 'package:twonly/src/views/chats/chat_messages_components/chat_message_entry_components/chat_media_entry.dart'; -import 'package:twonly/src/views/chats/chat_messages_components/chat_message_entry_components/chat_reaction_row.dart'; -import 'package:twonly/src/views/chats/chat_messages_components/chat_message_entry_components/chat_text_entry.dart'; -import 'package:twonly/src/views/chats/chat_messages_components/chat_message_entry_components/chat_text_response_columns.dart'; -import 'package:twonly/src/views/chats/chat_messages_components/sliding_response.dart'; -import 'package:twonly/src/database/twonly_database.dart'; -import 'package:twonly/src/model/json/message.dart'; - -class ChatListEntry extends StatefulWidget { - const ChatListEntry( - this.message, - this.contact, - this.lastMessageFromSameUser, - this.textReactions, - this.otherReactions, { - super.key, - required this.onResponseTriggered, - }); - final Message message; - final Contact contact; - final bool lastMessageFromSameUser; - final List textReactions; - final List otherReactions; - final Function(Message) onResponseTriggered; - - @override - State createState() => _ChatListEntryState(); -} - -class _ChatListEntryState extends State { - MessageContent? content; - String? textMessage; - - @override - void initState() { - super.initState(); - final msgContent = MessageContent.fromJson( - widget.message.kind, jsonDecode(widget.message.contentJson!)); - if (msgContent is TextMessageContent) { - textMessage = msgContent.text; - } - content = msgContent; - } - - @override - Widget build(BuildContext context) { - if (content == null) return Container(); - bool right = widget.message.messageOtherId == null; - - return Align( - alignment: right ? Alignment.centerRight : Alignment.centerLeft, - child: Padding( - padding: widget.lastMessageFromSameUser - ? EdgeInsets.only(top: 5, bottom: 0, right: 10, left: 10) - : EdgeInsets.only(top: 5, bottom: 20, right: 10, left: 10), - child: Column( - mainAxisAlignment: - right ? MainAxisAlignment.end : MainAxisAlignment.start, - crossAxisAlignment: - right ? CrossAxisAlignment.end : CrossAxisAlignment.start, - children: [ - SlidingResponse( - child: Stack( - alignment: right ? Alignment.centerRight : Alignment.centerLeft, - children: [ - (textMessage != null) - ? ChatTextEntry( - message: widget.message, text: textMessage!) - : ChatMediaEntry( - message: widget.message, - contact: widget.contact, - content: content!, - ), - Positioned( - bottom: 5, - left: 5, - right: 5, - child: ReactionRow( - otherReactions: widget.otherReactions, - message: widget.message, - ), - ), - ], - ), - onResponseTriggered: () { - widget.onResponseTriggered(widget.message); - }, - ), - ChatTextResponseColumns( - textReactions: widget.textReactions, - right: right, - ) - ], - ), - ), - ); - } -} diff --git a/lib/src/views/chats/chat_messages_components/chat_message_entry.dart b/lib/src/views/chats/chat_messages_components/chat_message_entry.dart index 5e9882b..8ccc9c2 100644 --- a/lib/src/views/chats/chat_messages_components/chat_message_entry.dart +++ b/lib/src/views/chats/chat_messages_components/chat_message_entry.dart @@ -7,11 +7,13 @@ import 'package:twonly/src/views/chats/chat_messages_components/chat_message_ent import 'package:twonly/src/views/chats/chat_messages_components/sliding_response.dart'; import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/model/json/message.dart'; +import 'package:twonly/src/views/gallery/gallery_main_view.dart'; class ChatListEntry extends StatefulWidget { const ChatListEntry( this.message, this.contact, + this.galleryItems, this.lastMessageFromSameUser, this.textReactions, this.otherReactions, { @@ -23,6 +25,7 @@ class ChatListEntry extends StatefulWidget { final bool lastMessageFromSameUser; final List textReactions; final List otherReactions; + final List galleryItems; final Function(Message) onResponseTriggered; @override @@ -49,52 +52,56 @@ class _ChatListEntryState extends State { if (content == null) return Container(); bool right = widget.message.messageOtherId == null; - return Align( - alignment: right ? Alignment.centerRight : Alignment.centerLeft, - child: Padding( - padding: widget.lastMessageFromSameUser - ? EdgeInsets.only(top: 5, bottom: 0, right: 10, left: 10) - : EdgeInsets.only(top: 5, bottom: 20, right: 10, left: 10), - child: Column( - mainAxisAlignment: - right ? MainAxisAlignment.end : MainAxisAlignment.start, - crossAxisAlignment: - right ? CrossAxisAlignment.end : CrossAxisAlignment.start, - children: [ - SlidingResponse( - child: Stack( - alignment: right ? Alignment.centerRight : Alignment.centerLeft, - children: [ - (textMessage != null) - ? ChatTextEntry( - message: widget.message, text: textMessage!) - : ChatMediaEntry( + return Hero( + tag: "${widget.message.mediaUploadId ?? widget.message.messageId}", + child: Align( + alignment: right ? Alignment.centerRight : Alignment.centerLeft, + child: Padding( + padding: widget.lastMessageFromSameUser + ? EdgeInsets.only(top: 5, bottom: 0, right: 10, left: 10) + : EdgeInsets.only(top: 5, bottom: 20, right: 10, left: 10), + child: Column( + mainAxisAlignment: + right ? MainAxisAlignment.end : MainAxisAlignment.start, + crossAxisAlignment: + right ? CrossAxisAlignment.end : CrossAxisAlignment.start, + children: [ + SlidingResponse( + child: Stack( + alignment: + right ? Alignment.centerRight : Alignment.centerLeft, + children: [ + (textMessage != null) + ? ChatTextEntry( + message: widget.message, text: textMessage!) + : ChatMediaEntry( + message: widget.message, + contact: widget.contact, + galleryItems: widget.galleryItems, + content: content!, + ), + Positioned( + bottom: 5, + left: 5, + right: 5, + child: ReactionRow( + otherReactions: widget.otherReactions, message: widget.message, - contact: widget.contact, - content: content!, ), - Positioned( - bottom: 5, - left: 5, - right: 5, - child: ReactionRow( - otherReactions: widget.otherReactions, - message: widget.message, - ), + ), + ], ), - ], - ), - onResponseTriggered: () { - widget.onResponseTriggered(widget.message); - }, + onResponseTriggered: () { + widget.onResponseTriggered(widget.message); + }, + ), + ChatTextResponseColumns( + textReactions: widget.textReactions, + right: right, + ) + ], ), - ChatTextResponseColumns( - textReactions: widget.textReactions, - right: right, - ) - ], - ), - ), - ); + ), + )); } } diff --git a/lib/src/views/chats/chat_messages_components/chat_message_entry_components/chat_media_entry.dart b/lib/src/views/chats/chat_messages_components/chat_message_entry_components/chat_media_entry.dart index d1af8e5..8c6e04e 100644 --- a/lib/src/views/chats/chat_messages_components/chat_message_entry_components/chat_media_entry.dart +++ b/lib/src/views/chats/chat_messages_components/chat_message_entry_components/chat_media_entry.dart @@ -9,6 +9,7 @@ import 'package:twonly/src/providers/api/api.dart'; import 'package:twonly/src/providers/api/media_received.dart' as received; import 'package:twonly/src/services/notification_service.dart'; import 'package:twonly/src/views/chats/media_viewer_view.dart'; +import 'package:twonly/src/views/gallery/gallery_main_view.dart'; class ChatMediaEntry extends StatelessWidget { const ChatMediaEntry({ @@ -16,11 +17,13 @@ class ChatMediaEntry extends StatelessWidget { required this.message, required this.contact, required this.content, + required this.galleryItems, }); final Message message; final Contact contact; final MessageContent content; + final List galleryItems; @override Widget build(BuildContext context) { @@ -70,15 +73,8 @@ class ChatMediaEntry extends StatelessWidget { } } }, - child: Container( + child: SizedBox( width: 150, - decoration: BoxDecoration( - border: Border.all( - color: color, - width: 1.0, - ), - borderRadius: BorderRadius.circular(12.0), - ), child: Align( alignment: Alignment.centerRight, child: ClipRRect( @@ -86,6 +82,8 @@ class ChatMediaEntry extends StatelessWidget { child: InChatMediaViewer( message: message, contact: contact, + color: color, + galleryItems: galleryItems, ), ), ), diff --git a/lib/src/views/chats/chat_messages_components/in_chat_media_viewer.dart b/lib/src/views/chats/chat_messages_components/in_chat_media_viewer.dart index 36feb22..a078ba6 100644 --- a/lib/src/views/chats/chat_messages_components/in_chat_media_viewer.dart +++ b/lib/src/views/chats/chat_messages_components/in_chat_media_viewer.dart @@ -14,110 +14,121 @@ import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/database/tables/messages_table.dart'; import 'package:twonly/src/model/json/message.dart'; import 'package:twonly/src/providers/api/media_received.dart' as received; +import 'package:twonly/src/views/gallery/gallery_main_view.dart'; import 'package:video_player/video_player.dart'; -class ChatMediaViewerFullScreen extends StatefulWidget { - const ChatMediaViewerFullScreen( - {super.key, required this.message, required this.contact}); - final Message message; - final Contact contact; +// class ChatMediaViewerFullScreen extends StatefulWidget { +// const ChatMediaViewerFullScreen({ +// super.key, +// required this.message, +// required this.contact, +// required this.color, +// }); +// final Message message; +// final Contact contact; +// final Color color; - @override - State createState() => - _ChatMediaViewerFullScreenState(); -} +// @override +// State createState() => +// _ChatMediaViewerFullScreenState(); +// } -class _ChatMediaViewerFullScreenState extends State { - bool hideMediaFile = false; +// class _ChatMediaViewerFullScreenState extends State { +// bool hideMediaFile = false; - Future deleteFiles(context) async { - bool confirmed = await showAlertDialog( - context, "Are you sure?", "The image will be irrevocably deleted."); +// Future deleteFiles(context) async { +// bool confirmed = await showAlertDialog( +// context, "Are you sure?", "The image will be irrevocably deleted."); - if (!confirmed) return; +// if (!confirmed) return; - await twonlyDatabase.messagesDao.updateMessageByMessageId( - widget.message.messageId, - MessagesCompanion(mediaStored: Value(false)), - ); - await send.purgeSendMediaFiles(); - await received.purgeReceivedMediaFiles(); - if (context.mounted) { - Navigator.pop(context, true); - } - } +// await twonlyDatabase.messagesDao.updateMessageByMessageId( +// widget.message.messageId, +// MessagesCompanion(mediaStored: Value(false)), +// ); +// await send.purgeSendMediaFiles(); +// await received.purgeReceivedMediaFiles(); +// if (context.mounted) { +// Navigator.pop(context, true); +// } +// } - @override - Widget build(BuildContext context) { - return Scaffold( - body: MediaViewSizing( - bottomNavigation: Positioned( - bottom: 10, - left: 0, - right: 0, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - IconButton.outlined( - onPressed: () { - deleteFiles(context); - }, - icon: FaIcon(FontAwesomeIcons.trashCan), - style: ButtonStyle( - padding: WidgetStateProperty.all( - EdgeInsets.symmetric(vertical: 10, horizontal: 20), - ), - ), - ), - IconButton.filled( - icon: FaIcon(FontAwesomeIcons.camera), - onPressed: () async { - setState(() { - hideMediaFile = true; - }); - await Navigator.push(context, MaterialPageRoute( - builder: (context) { - return CameraSendToView(widget.contact); - }, - )); - setState(() { - hideMediaFile = false; - }); - }, - style: ButtonStyle( - padding: WidgetStateProperty.all( - EdgeInsets.symmetric(vertical: 10, horizontal: 30), - ), - backgroundColor: WidgetStateProperty.all( - Theme.of(context).colorScheme.primary, - )), - ), - ], - ), - ), - child: (hideMediaFile) - ? Container() - : InChatMediaViewer( - message: widget.message, - contact: widget.contact, - isInFullscreen: true, - ), - ), - ); - } -} +// @override +// Widget build(BuildContext context) { +// return Scaffold( +// body: MediaViewSizing( +// bottomNavigation: Positioned( +// bottom: 10, +// left: 0, +// right: 0, +// child: Row( +// mainAxisAlignment: MainAxisAlignment.spaceEvenly, +// children: [ +// IconButton.outlined( +// onPressed: () { +// deleteFiles(context); +// }, +// icon: FaIcon(FontAwesomeIcons.trashCan), +// style: ButtonStyle( +// padding: WidgetStateProperty.all( +// EdgeInsets.symmetric(vertical: 10, horizontal: 20), +// ), +// ), +// ), +// IconButton.filled( +// icon: FaIcon(FontAwesomeIcons.camera), +// onPressed: () async { +// setState(() { +// hideMediaFile = true; +// }); +// await Navigator.push(context, MaterialPageRoute( +// builder: (context) { +// return CameraSendToView(widget.contact); +// }, +// )); +// setState(() { +// hideMediaFile = false; +// }); +// }, +// style: ButtonStyle( +// padding: WidgetStateProperty.all( +// EdgeInsets.symmetric(vertical: 10, horizontal: 30), +// ), +// backgroundColor: WidgetStateProperty.all( +// Theme.of(context).colorScheme.primary, +// )), +// ), +// ], +// ), +// ), +// child: (hideMediaFile) +// ? Container() +// : Hero( +// tag: "chat_entry_${widget.message.messageId}", +// child: InChatMediaViewer( +// message: widget.message, +// contact: widget.contact, +// color: widget.color, +// isInFullscreen: true, +// ), +// )), +// ); +// } +// } class InChatMediaViewer extends StatefulWidget { const InChatMediaViewer({ super.key, required this.message, required this.contact, - this.isInFullscreen = false, + required this.color, + required this.galleryItems, }); final Message message; final Contact contact; - final bool isInFullscreen; + final List galleryItems; + final Color color; @override State createState() => _InChatMediaViewerState(); @@ -184,13 +195,10 @@ class _InChatMediaViewerState extends State { } videoController = VideoPlayerController.file( videoPath, - videoPlayerOptions: - VideoPlayerOptions(mixWithOthers: !widget.isInFullscreen), + videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true), ); videoController?.initialize().then((_) { - if (!widget.isInFullscreen) { - videoController!.setVolume(0); - } + videoController!.setVolume(0); videoController!.play(); videoController!.setLooping(true); }); @@ -209,51 +217,84 @@ class _InChatMediaViewerState extends State { } Future onTap() async { - bool? removed = await Navigator.push( + Navigator.push( context, - MaterialPageRoute(builder: (context) { - return ChatMediaViewerFullScreen( - message: widget.message, - contact: widget.contact, - ); - }), + MaterialPageRoute( + builder: (context) => GalleryPhotoViewWrapper( + galleryItems: widget.galleryItems, + // backgroundDecoration: const BoxDecoration( + // color: Colors.black, + // ), + initialIndex: widget.galleryItems.indexWhere((x) => + x.id == + (widget.message.mediaUploadId ?? widget.message.messageId) + .toString()), + scrollDirection: Axis.horizontal, + ), + ), ); + // bool? removed = await Navigator.push( + // context, + // MaterialPageRoute(builder: (context) { + // return ChatMediaViewerFullScreen( + // message: widget.message, + // contact: widget.contact, + // color: widget.color, + // ); + // }), + // ); - if (removed != null && removed) { - image = null; - videoController?.dispose(); - videoController = null; - if (isMounted) setState(() {}); - } + // if (removed != null && removed) { + // image = null; + // videoController?.dispose(); + // videoController = null; + // if (isMounted) setState(() {}); + // } } @override Widget build(BuildContext context) { if (image == null && video == null) { - return Padding( - padding: const EdgeInsets.all(10.0), - child: MessageSendStateIcon( - [widget.message], - mainAxisAlignment: MainAxisAlignment.center, + return Container( + decoration: BoxDecoration( + border: Border.all( + color: widget.color, + width: 1.0, + ), + borderRadius: BorderRadius.circular(12.0), + ), + child: Padding( + padding: const EdgeInsets.all(10.0), + child: MessageSendStateIcon( + [widget.message], + mainAxisAlignment: MainAxisAlignment.center, + ), ), ); } - return GestureDetector( - onTap: - ((image == null && videoController == null) || widget.isInFullscreen) - ? null - : onTap, - child: Stack( - children: [ - if (image != null) Image.file(image!), - if (videoController != null) - Positioned.fill( - child: Transform.flip( - flipX: mirrorVideo, - child: VideoPlayer(videoController!), + return Container( + decoration: BoxDecoration( + border: Border.all( + color: Colors.transparent, + width: 1.0, + ), + color: Colors.transparent, + borderRadius: BorderRadius.circular(12.0), + ), + child: GestureDetector( + onTap: ((image == null && videoController == null)) ? null : onTap, + child: Stack( + children: [ + if (image != null) Image.file(image!), + if (videoController != null) + Positioned.fill( + child: Transform.flip( + flipX: mirrorVideo, + child: VideoPlayer(videoController!), + ), ), - ), - ], + ], + ), ), ); } diff --git a/lib/src/views/chats/chat_messages_view.dart b/lib/src/views/chats/chat_messages_view.dart index f4f2c42..949fec9 100644 --- a/lib/src/views/chats/chat_messages_view.dart +++ b/lib/src/views/chats/chat_messages_view.dart @@ -18,6 +18,7 @@ import 'package:twonly/src/services/notification_service.dart'; import 'package:twonly/src/views/camera/camera_send_to_view.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/contact/contact_view.dart'; +import 'package:twonly/src/views/gallery/gallery_main_view.dart'; Color getMessageColor(Message message) { return (message.messageOtherId == null) @@ -43,6 +44,7 @@ class _ChatMessagesViewState extends State { late StreamSubscription userSub; late StreamSubscription> messageSub; List messages = []; + List galleryItems = []; Map> textReactionsToMessageId = {}; Map> emojiReactionsToMessageId = {}; Message? responseToMessage; @@ -76,7 +78,7 @@ class _ChatMessagesViewState extends State { Stream> msgStream = twonlyDatabase.messagesDao.watchAllMessagesFrom(widget.contact.userId); - messageSub = msgStream.listen((msgs) { + messageSub = msgStream.listen((msgs) async { // if (!context.mounted) return; if (Platform.isAndroid) { flutterLocalNotificationsPlugin.cancel(widget.contact.userId); @@ -131,6 +133,7 @@ class _ChatMessagesViewState extends State { displayedMessages.add(msg); } } + if (openedMessageOtherIds.isNotEmpty) { notifyContactAboutOpeningMessage( widget.contact.userId, openedMessageOtherIds); @@ -145,7 +148,15 @@ class _ChatMessagesViewState extends State { emojiReactionsToMessageId = tmpEmojiReactionsToMessageId; messages = displayedMessages; }); - // } + Map items = await GalleryItem.convertFromMessages( + displayedMessages + .where((x) => x.kind == MessageKind.media) + .toList() + .reversed + .toList()); + setState(() { + galleryItems = items.values.toList(); + }); }); } @@ -299,6 +310,7 @@ class _ChatMessagesViewState extends State { key: Key(messages[i].messageId.toString()), messages[i], user, + galleryItems, isLastMessageFromSameUser(messages, i), textReactionsToMessageId[messages[i].messageId] ?? [], emojiReactionsToMessageId[messages[i].messageId] ?? [], diff --git a/lib/src/views/gallery/gallery_main_view.dart b/lib/src/views/gallery/gallery_main_view.dart index 1f6bc76..183bf3d 100644 --- a/lib/src/views/gallery/gallery_main_view.dart +++ b/lib/src/views/gallery/gallery_main_view.dart @@ -26,6 +26,40 @@ class GalleryItem { final DateTime date; final File? imagePath; final File? videoPath; + + static Future> convertFromMessages( + List messages) async { + Map items = {}; + for (final message in messages) { + bool isSend = message.messageOtherId == null; + int id = message.mediaUploadId ?? message.messageId; + final basePath = await send.getMediaFilePath( + isSend ? message.mediaUploadId! : message.messageId, + isSend ? "send" : "received", + ); + File? imagePath; + File? videoPath; + if (await File("$basePath.mp4").exists()) { + videoPath = File("$basePath.mp4"); + } else if (await File("$basePath.png").exists()) { + imagePath = File("$basePath.png"); + } else { + continue; + } + items + .putIfAbsent( + id, + () => GalleryItem( + id: id.toString(), + messages: [], + date: message.sendAt, + imagePath: imagePath, + videoPath: videoPath)) + .messages + .add(message); + } + return items; + } } class GalleryItemGrid { @@ -180,40 +214,12 @@ class GalleryMainViewState extends State { List storedMediaFiles = await twonlyDatabase.messagesDao.getAllStoredMediaFiles(); - Map items = {}; - for (final message in storedMediaFiles) { - bool isSend = message.messageOtherId == null; - int id = message.mediaUploadId ?? message.messageId; - final basePath = await send.getMediaFilePath( - isSend ? message.mediaUploadId! : message.messageId, - isSend ? "send" : "received", - ); - File? imagePath; - File? videoPath; - if (await File("$basePath.mp4").exists()) { - videoPath = File("$basePath.mp4"); - } else if (await File("$basePath.png").exists()) { - imagePath = File("$basePath.png"); - } else { - continue; - } - items - .putIfAbsent( - id, - () => GalleryItem( - id: id.toString(), - messages: [], - date: message.sendAt, - imagePath: imagePath, - videoPath: videoPath)) - .messages - .add(message); - } + Map items = + await GalleryItem.convertFromMessages(storedMediaFiles); // Group items by month orderedByMonth = {}; months = []; - String lastMonth = ""; galleryItems = await loadMemoriesDirectory(); galleryItems += items.values.toList();