Implement View Replies

This commit is contained in:
Sarah Jamie Lewis 2022-07-07 12:34:31 -07:00
parent 62ea8278f3
commit 814e6df6f6
18 changed files with 259 additions and 19 deletions

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -201,6 +201,42 @@ class ByContentHash implements CacheHandler {
}
}
List<Message> getReplies(MessageCache cache, int messageIdentifier) {
List<Message> 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<Message> messageHandler(BuildContext context, String profileOnion, int conversationIdentifier, CacheHandler cacheHandler) async {
var malformedMetadata = MessageMetadata(profileOnion, conversationIdentifier, 0, DateTime.now(), "", "", "", <String, String>{}, false, true, false, "");
var cwtch = Provider.of<FlwtchState>(context, listen: false).cwtch;

View File

@ -36,7 +36,6 @@ class QuotedMessage extends Message {
var content = message["body"];
return Text(
content,
overflow: TextOverflow.ellipsis,
);
} catch (e) {
return MalformedBubble();

View File

@ -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<MessageRow> with SingleTickerProviderStateMi
}
}
Widget wdgIcons = Platform.isAndroid
Widget wdgReply = Platform.isAndroid
? SizedBox.shrink()
: Visibility(
visible: EnvironmentConfig.TEST_MODE || Provider.of<AppState>(context).hoveredIndex == Provider.of<MessageMetadata>(context).messageID,
@ -90,13 +92,37 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
},
icon: Icon(Icons.reply, color: Provider.of<Settings>(context).theme.dropShadowColor)));
var settings = Provider.of<Settings>(context);
var pis = Provider.of<ProfileInfoState>(context);
var cis = Provider.of<ContactInfoState>(context);
var borderColor = Provider.of<Settings>(context).theme.portraitOnlineBorderColor;
var messageID = Provider.of<MessageMetadata>(context).messageID;
var cache = Provider.of<ContactInfoState>(context).messageCache;
Widget wdgSeeReplies = Platform.isAndroid
? SizedBox.shrink()
: Visibility(
visible: EnvironmentConfig.TEST_MODE || Provider.of<AppState>(context).hoveredIndex == Provider.of<MessageMetadata>(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<Settings>(context).theme.dropShadowColor)));
Widget wdgSpacer = Flexible(flex: 1, child: SizedBox(width: Platform.isAndroid ? 20 : 60, height: 10));
var widgetRow = <Widget>[];
if (fromMe) {
widgetRow = <Widget>[
wdgSpacer,
wdgIcons,
wdgSeeReplies,
wdgReply,
actualMessage,
];
} else if (isBlocked && !showBlockedMessage) {
@ -143,7 +169,8 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
});
})),
]))),
wdgIcons,
wdgReply,
wdgSeeReplies,
wdgSpacer,
];
} else {
@ -179,7 +206,8 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
widgetRow = <Widget>[
wdgPortrait,
actualMessage,
wdgIcons,
wdgReply,
wdgSeeReplies,
wdgSpacer,
];
}
@ -332,3 +360,80 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
);
}
}
void modalShowReplies(BuildContext ctx, String replyHeader, Settings settings, ProfileInfoState profile, ContactInfoState cis, Color borderColor, MessageCache cache, int messageID,
{bool showImage = true}) {
showModalBottomSheet<void>(
context: ctx,
builder: (BuildContext bcontext) {
List<Message> 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,
))));
});
});
}

View File

@ -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<StaticMessageBubble> {
@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])))));
});
}
}