forked from cwtch.im/cwtch-ui
Implement View Replies
This commit is contained in:
parent
62ea8278f3
commit
814e6df6f6
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -36,7 +36,6 @@ class QuotedMessage extends Message {
|
|||
var content = message["body"];
|
||||
return Text(
|
||||
content,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
);
|
||||
} catch (e) {
|
||||
return MalformedBubble();
|
||||
|
|
|
@ -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,
|
||||
))));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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])))));
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue