From 814e6df6f6211db8b9c2a89968c98254e21f51cb Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Thu, 7 Jul 2022 12:34:31 -0700 Subject: [PATCH] Implement View Replies --- lib/l10n/intl_cy.arb | 4 +- lib/l10n/intl_da.arb | 4 +- lib/l10n/intl_de.arb | 4 +- lib/l10n/intl_el.arb | 4 +- lib/l10n/intl_en.arb | 4 +- lib/l10n/intl_es.arb | 4 +- lib/l10n/intl_fr.arb | 4 +- lib/l10n/intl_it.arb | 4 +- lib/l10n/intl_lb.arb | 4 +- lib/l10n/intl_no.arb | 4 +- lib/l10n/intl_pl.arb | 4 +- lib/l10n/intl_pt.arb | 4 +- lib/l10n/intl_ro.arb | 4 +- lib/l10n/intl_ru.arb | 4 +- lib/models/message.dart | 36 ++++++++ lib/models/messages/quotedmessage.dart | 1 - lib/widgets/messagerow.dart | 113 ++++++++++++++++++++++++- lib/widgets/staticmessagebubble.dart | 72 ++++++++++++++++ 18 files changed, 259 insertions(+), 19 deletions(-) create mode 100644 lib/widgets/staticmessagebubble.dart diff --git a/lib/l10n/intl_cy.arb b/lib/l10n/intl_cy.arb index 80fcdb37..4e58d39c 100644 --- a/lib/l10n/intl_cy.arb +++ b/lib/l10n/intl_cy.arb @@ -1,6 +1,8 @@ { "@@locale": "cy", - "@@last_modified": "2022-07-06T20:42:11+02:00", + "@@last_modified": "2022-07-07T21:07:20+02:00", + "headingReplies": "Replies", + "viewReplies": "View replies to this message", "restartFileShare": "Start Sharing File", "stopSharingFile": "Stop Sharing File", "manageSharedFiles": "Manage Shared Files", diff --git a/lib/l10n/intl_da.arb b/lib/l10n/intl_da.arb index d2c9e9af..a3200930 100644 --- a/lib/l10n/intl_da.arb +++ b/lib/l10n/intl_da.arb @@ -1,6 +1,8 @@ { "@@locale": "da", - "@@last_modified": "2022-07-06T20:42:11+02:00", + "@@last_modified": "2022-07-07T21:07:20+02:00", + "headingReplies": "Replies", + "viewReplies": "View replies to this message", "restartFileShare": "Start Sharing File", "stopSharingFile": "Stop Sharing File", "manageSharedFiles": "Manage Shared Files", diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb index 17090263..6a51c81f 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -1,6 +1,8 @@ { "@@locale": "de", - "@@last_modified": "2022-07-06T20:42:11+02:00", + "@@last_modified": "2022-07-07T21:07:20+02:00", + "headingReplies": "Replies", + "viewReplies": "View replies to this message", "restartFileShare": "Start Sharing File", "stopSharingFile": "Stop Sharing File", "manageSharedFiles": "Manage Shared Files", diff --git a/lib/l10n/intl_el.arb b/lib/l10n/intl_el.arb index 27a108e8..842c0254 100644 --- a/lib/l10n/intl_el.arb +++ b/lib/l10n/intl_el.arb @@ -1,6 +1,8 @@ { "@@locale": "el", - "@@last_modified": "2022-07-06T20:42:11+02:00", + "@@last_modified": "2022-07-07T21:07:20+02:00", + "headingReplies": "Replies", + "viewReplies": "View replies to this message", "restartFileShare": "Start Sharing File", "stopSharingFile": "Stop Sharing File", "manageSharedFiles": "Manage Shared Files", diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 1f3e4da7..9c44e787 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -1,6 +1,8 @@ { "@@locale": "en", - "@@last_modified": "2022-07-06T20:42:11+02:00", + "@@last_modified": "2022-07-07T21:07:20+02:00", + "headingReplies": "Replies", + "viewReplies": "View replies to this message", "restartFileShare": "Start Sharing File", "stopSharingFile": "Stop Sharing File", "manageSharedFiles": "Manage Shared Files", diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index 35227f35..fd08919b 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -1,6 +1,8 @@ { "@@locale": "es", - "@@last_modified": "2022-07-06T20:42:11+02:00", + "@@last_modified": "2022-07-07T21:07:20+02:00", + "headingReplies": "Replies", + "viewReplies": "View replies to this message", "restartFileShare": "Start Sharing File", "stopSharingFile": "Stop Sharing File", "manageSharedFiles": "Manage Shared Files", diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index e77e678d..14d62509 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -1,6 +1,8 @@ { "@@locale": "fr", - "@@last_modified": "2022-07-06T20:42:11+02:00", + "@@last_modified": "2022-07-07T21:07:20+02:00", + "headingReplies": "Replies", + "viewReplies": "View replies to this message", "restartFileShare": "Start Sharing File", "stopSharingFile": "Stop Sharing File", "manageSharedFiles": "Manage Shared Files", diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb index 65630157..c8250f9b 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -1,6 +1,8 @@ { "@@locale": "it", - "@@last_modified": "2022-07-06T20:42:11+02:00", + "@@last_modified": "2022-07-07T21:07:20+02:00", + "headingReplies": "Replies", + "viewReplies": "View replies to this message", "restartFileShare": "Start Sharing File", "stopSharingFile": "Stop Sharing File", "manageSharedFiles": "Manage Shared Files", diff --git a/lib/l10n/intl_lb.arb b/lib/l10n/intl_lb.arb index 27eb26c0..bee2b7e6 100644 --- a/lib/l10n/intl_lb.arb +++ b/lib/l10n/intl_lb.arb @@ -1,6 +1,8 @@ { "@@locale": "lb", - "@@last_modified": "2022-07-06T20:42:11+02:00", + "@@last_modified": "2022-07-07T21:07:20+02:00", + "headingReplies": "Replies", + "viewReplies": "View replies to this message", "restartFileShare": "Start Sharing File", "stopSharingFile": "Stop Sharing File", "manageSharedFiles": "Manage Shared Files", diff --git a/lib/l10n/intl_no.arb b/lib/l10n/intl_no.arb index 3dde1ad9..d13492a7 100644 --- a/lib/l10n/intl_no.arb +++ b/lib/l10n/intl_no.arb @@ -1,6 +1,8 @@ { "@@locale": "no", - "@@last_modified": "2022-07-06T20:42:11+02:00", + "@@last_modified": "2022-07-07T21:07:20+02:00", + "headingReplies": "Replies", + "viewReplies": "View replies to this message", "restartFileShare": "Start Sharing File", "stopSharingFile": "Stop Sharing File", "manageSharedFiles": "Manage Shared Files", diff --git a/lib/l10n/intl_pl.arb b/lib/l10n/intl_pl.arb index 4d2d8ec8..b85854fe 100644 --- a/lib/l10n/intl_pl.arb +++ b/lib/l10n/intl_pl.arb @@ -1,6 +1,8 @@ { "@@locale": "pl", - "@@last_modified": "2022-07-06T20:42:11+02:00", + "@@last_modified": "2022-07-07T21:07:20+02:00", + "headingReplies": "Replies", + "viewReplies": "View replies to this message", "restartFileShare": "Start Sharing File", "stopSharingFile": "Stop Sharing File", "manageSharedFiles": "Manage Shared Files", diff --git a/lib/l10n/intl_pt.arb b/lib/l10n/intl_pt.arb index 9c6857b9..c5e363b3 100644 --- a/lib/l10n/intl_pt.arb +++ b/lib/l10n/intl_pt.arb @@ -1,6 +1,8 @@ { "@@locale": "pt", - "@@last_modified": "2022-07-06T20:42:11+02:00", + "@@last_modified": "2022-07-07T21:07:20+02:00", + "headingReplies": "Replies", + "viewReplies": "View replies to this message", "restartFileShare": "Start Sharing File", "stopSharingFile": "Stop Sharing File", "manageSharedFiles": "Manage Shared Files", diff --git a/lib/l10n/intl_ro.arb b/lib/l10n/intl_ro.arb index 6126bf74..fb756dfa 100644 --- a/lib/l10n/intl_ro.arb +++ b/lib/l10n/intl_ro.arb @@ -1,6 +1,8 @@ { "@@locale": "ro", - "@@last_modified": "2022-07-06T20:42:11+02:00", + "@@last_modified": "2022-07-07T21:07:20+02:00", + "headingReplies": "Replies", + "viewReplies": "View replies to this message", "restartFileShare": "Start Sharing File", "stopSharingFile": "Stop Sharing File", "manageSharedFiles": "Manage Shared Files", diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index 6d72fe2e..d0a225d1 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -1,6 +1,8 @@ { "@@locale": "ru", - "@@last_modified": "2022-07-06T20:42:11+02:00", + "@@last_modified": "2022-07-07T21:07:20+02:00", + "headingReplies": "Replies", + "viewReplies": "View replies to this message", "restartFileShare": "Start Sharing File", "stopSharingFile": "Stop Sharing File", "manageSharedFiles": "Manage Shared Files", diff --git a/lib/models/message.dart b/lib/models/message.dart index 7d169698..68721bc7 100644 --- a/lib/models/message.dart +++ b/lib/models/message.dart @@ -201,6 +201,42 @@ class ByContentHash implements CacheHandler { } } +List getReplies(MessageCache cache, int messageIdentifier) { + List replies = List.empty(growable: true); + + try { + MessageInfo original = cache.cache[messageIdentifier]!; + String hash = original.metadata.contenthash; + + cache.cache.forEach((key, messageInfo) { + // only bother searching for identifiers that came *after* + if (key > messageIdentifier) { + try { + dynamic message = jsonDecode(messageInfo.wrapper); + var content = message['d'] as dynamic; + dynamic qmessage = jsonDecode(content); + if (qmessage["body"] == null || qmessage["quotedHash"] == null) { + return; + } + if (qmessage["quotedHash"] == hash) { + replies.add(compileOverlay(messageInfo)); + } + } catch (e) { + // ignore + } + } + }); + } catch (e) { + EnvironmentConfig.debugLog("message handler exception on get from cache: $e"); + } + + replies.sort((a, b) { + return a.getMetadata().messageID.compareTo(b.getMetadata().messageID); + }); + + return replies; +} + Future messageHandler(BuildContext context, String profileOnion, int conversationIdentifier, CacheHandler cacheHandler) async { var malformedMetadata = MessageMetadata(profileOnion, conversationIdentifier, 0, DateTime.now(), "", "", "", {}, false, true, false, ""); var cwtch = Provider.of(context, listen: false).cwtch; diff --git a/lib/models/messages/quotedmessage.dart b/lib/models/messages/quotedmessage.dart index 471ed536..9f819618 100644 --- a/lib/models/messages/quotedmessage.dart +++ b/lib/models/messages/quotedmessage.dart @@ -36,7 +36,6 @@ class QuotedMessage extends Message { var content = message["body"]; return Text( content, - overflow: TextOverflow.ellipsis, ); } catch (e) { return MalformedBubble(); diff --git a/lib/widgets/messagerow.dart b/lib/widgets/messagerow.dart index 3bbb8b87..d34f26bf 100644 --- a/lib/widgets/messagerow.dart +++ b/lib/widgets/messagerow.dart @@ -7,6 +7,7 @@ import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/message.dart'; import 'package:cwtch/models/profile.dart'; import 'package:cwtch/views/contactsview.dart'; +import 'package:cwtch/widgets/staticmessagebubble.dart'; import 'package:flutter/material.dart'; import 'package:cwtch/widgets/profileimage.dart'; import 'package:flutter/physics.dart'; @@ -15,6 +16,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import '../main.dart'; +import '../models/messagecache.dart'; import '../settings.dart'; class MessageRow extends StatefulWidget { @@ -74,7 +76,7 @@ class MessageRowState extends State with SingleTickerProviderStateMi } } - Widget wdgIcons = Platform.isAndroid + Widget wdgReply = Platform.isAndroid ? SizedBox.shrink() : Visibility( visible: EnvironmentConfig.TEST_MODE || Provider.of(context).hoveredIndex == Provider.of(context).messageID, @@ -90,13 +92,37 @@ class MessageRowState extends State with SingleTickerProviderStateMi }, icon: Icon(Icons.reply, color: Provider.of(context).theme.dropShadowColor))); + var settings = Provider.of(context); + var pis = Provider.of(context); + var cis = Provider.of(context); + var borderColor = Provider.of(context).theme.portraitOnlineBorderColor; + var messageID = Provider.of(context).messageID; + var cache = Provider.of(context).messageCache; + + Widget wdgSeeReplies = Platform.isAndroid + ? SizedBox.shrink() + : Visibility( + visible: EnvironmentConfig.TEST_MODE || Provider.of(context).hoveredIndex == Provider.of(context).messageID, + maintainSize: true, + maintainAnimation: true, + maintainState: true, + maintainInteractivity: false, + child: IconButton( + tooltip: AppLocalizations.of(context)!.viewReplies, + splashRadius: Material.defaultSplashRadius / 2, + onPressed: () { + modalShowReplies(context, AppLocalizations.of(context)!.headingReplies, settings, pis, cis, borderColor, cache, messageID); + }, + icon: Icon(Icons.message_rounded, color: Provider.of(context).theme.dropShadowColor))); + Widget wdgSpacer = Flexible(flex: 1, child: SizedBox(width: Platform.isAndroid ? 20 : 60, height: 10)); var widgetRow = []; if (fromMe) { widgetRow = [ wdgSpacer, - wdgIcons, + wdgSeeReplies, + wdgReply, actualMessage, ]; } else if (isBlocked && !showBlockedMessage) { @@ -143,7 +169,8 @@ class MessageRowState extends State with SingleTickerProviderStateMi }); })), ]))), - wdgIcons, + wdgReply, + wdgSeeReplies, wdgSpacer, ]; } else { @@ -179,7 +206,8 @@ class MessageRowState extends State with SingleTickerProviderStateMi widgetRow = [ wdgPortrait, actualMessage, - wdgIcons, + wdgReply, + wdgSeeReplies, wdgSpacer, ]; } @@ -332,3 +360,80 @@ class MessageRowState extends State with SingleTickerProviderStateMi ); } } + +void modalShowReplies(BuildContext ctx, String replyHeader, Settings settings, ProfileInfoState profile, ContactInfoState cis, Color borderColor, MessageCache cache, int messageID, + {bool showImage = true}) { + showModalBottomSheet( + context: ctx, + builder: (BuildContext bcontext) { + List replies = getReplies(cache, messageID); + + return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) { + var replyWidgets = replies.map((e) { + var fromMe = e.getMetadata().senderHandle == profile.onion; + + var bubble = StaticMessageBubble(profile, settings, e.getMetadata(), Row(children: [Flexible(child: e.getPreviewWidget(context))])); + + String imagePath = e.getMetadata().senderImage!; + var sender = profile.contactList.findContact(e.getMetadata().senderHandle); + if (sender != null) { + imagePath = showImage ? sender.imagePath : sender.defaultImagePath; + } + + if (fromMe) { + imagePath = profile.imagePath; + } + + var image = Padding( + padding: EdgeInsets.all(4.0), + child: ProfileImage( + imagePath: imagePath, + diameter: 48.0, + border: borderColor, + badgeTextColor: Colors.red, + badgeColor: Colors.red, + )); + + return Padding( + padding: EdgeInsets.all(10.0), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [image, Flexible(child: bubble)], + )); + }).toList(); + + var withHeader = replyWidgets; + + var original = StaticMessageBubble(profile, settings, cache.cache[messageID]!.metadata, Row(children: [Flexible(child: compileOverlay(cache.cache[messageID]!).getPreviewWidget(context))])); + + withHeader.insert(0, + Padding(padding: EdgeInsets.fromLTRB(10.0, 10.0, 2.0, 15.0), child: Center(child: original))); + + + withHeader.insert(1, + Padding(padding: EdgeInsets.fromLTRB(10.0, 10.0, 2.0, 15.0), + child: Divider( + color: settings.theme.mainTextColor, + )) + ); + + withHeader.insert(2, + Padding(padding: EdgeInsets.fromLTRB(10.0, 10.0, 2.0, 15.0), child: Text(replyHeader, style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)))); + + + + return Scrollbar( + isAlwaysShown: true, + child: SingleChildScrollView( + clipBehavior: Clip.antiAlias, + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: withHeader, + )))); + }); + }); +} diff --git a/lib/widgets/staticmessagebubble.dart b/lib/widgets/staticmessagebubble.dart new file mode 100644 index 00000000..d5e64c8c --- /dev/null +++ b/lib/widgets/staticmessagebubble.dart @@ -0,0 +1,72 @@ +import 'package:cwtch/models/contact.dart'; +import 'package:cwtch/models/message.dart'; +import 'package:cwtch/models/profile.dart'; +import 'package:cwtch/widgets/malformedbubble.dart'; +import 'package:flutter/material.dart'; + +import '../settings.dart'; +import 'messagebubbledecorations.dart'; + +class StaticMessageBubble extends StatefulWidget { + final ProfileInfoState profile; + final Settings settings; + final MessageMetadata metadata; + final Widget child; + + StaticMessageBubble(this.profile, this.settings, this.metadata, this.child); + + @override + StaticMessageBubbleState createState() => StaticMessageBubbleState(); +} + +class StaticMessageBubbleState extends State { + @override + Widget build(BuildContext context) { + var fromMe = widget.metadata.senderHandle == widget.profile.onion; + var borderRadiousEh = 15.0; + DateTime messageDate = widget.metadata.timestamp; + + // If the sender is not us, then we want to give them a nickname... + var senderDisplayStr = ""; + if (!fromMe) { + ContactInfoState? contact = widget.profile.contactList.findContact(widget.metadata.senderHandle); + if (contact != null) { + senderDisplayStr = contact.nickname; + } else { + senderDisplayStr = widget.metadata.senderHandle; + } + } else { + senderDisplayStr = widget.profile.nickname; + } + + var wdgSender = SelectableText(senderDisplayStr, style: TextStyle(fontSize: 9.0, color: fromMe ? widget.settings.theme.messageFromMeTextColor : widget.settings.theme.messageFromOtherTextColor)); + + var wdgDecorations = MessageBubbleDecoration(ackd: widget.metadata.ackd, errored: widget.metadata.error, fromMe: fromMe, messageDate: messageDate); + + var error = widget.metadata.error; + + return LayoutBuilder(builder: (context, constraints) { + //print(constraints.toString()+", "+constraints.maxWidth.toString()); + return RepaintBoundary( + child: Container( + child: Container( + decoration: BoxDecoration( + color: error ? malformedColor : (fromMe ? widget.settings.theme.messageFromMeBackgroundColor : widget.settings.theme.messageFromOtherBackgroundColor), + border: Border.all(color: error ? malformedColor : (fromMe ? widget.settings.theme.messageFromMeBackgroundColor : widget.settings.theme.messageFromOtherBackgroundColor), width: 1), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(borderRadiousEh), + topRight: Radius.circular(borderRadiousEh), + bottomLeft: Radius.zero, + bottomRight: Radius.circular(borderRadiousEh), + ), + ), + child: Padding( + padding: EdgeInsets.all(9.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [wdgSender, widget.child, wdgDecorations]))))); + }); + } +}