Addressing Dans comments + Marcia design
continuous-integration/drone/pr Build is running Details

This commit is contained in:
Sarah Jamie Lewis 2021-07-07 10:05:25 -07:00
parent 29c9c1615b
commit e4046fb574
17 changed files with 109 additions and 54 deletions

View File

@ -1 +1 @@
v1.0.0-70-gafa6794-2021-07-07-01-24 v1.0.0-25-g801a805-2021-07-07-16-10

View File

@ -126,7 +126,7 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
val profile = (a.get("profile") as? String) ?: "" val profile = (a.get("profile") as? String) ?: ""
val handle = (a.get("contact") as? String) ?: "" val handle = (a.get("contact") as? String) ?: ""
val contentHash = (a.get("contentHash") as? String) ?: "" val contentHash = (a.get("contentHash") as? String) ?: ""
return Result.success(Data.Builder().putString("result", Cwtch.getMessageByContentHash(profile, handle, contentHash)).build()) return Result.success(Data.Builder().putString("result", Cwtch.getMessagesByContentHash(profile, handle, contentHash)).build())
} }
"UpdateMessageFlags" -> { "UpdateMessageFlags" -> {
val profile = (a.get("profile") as? String) ?: "" val profile = (a.get("profile") as? String) ?: ""

View File

@ -1,6 +1,8 @@
{ {
"@@locale": "de", "@@locale": "de",
"@@last_modified": "2021-07-05T21:26:10+02:00", "@@last_modified": "2021-07-07T18:42:50+02:00",
"tooltipRemoveThisQuotedMessage": "Remove quoted message.",
"tooltipReplyToThisMessage": "Reply to this message",
"tooltipRejectContactRequest": "Reject this contact request", "tooltipRejectContactRequest": "Reject this contact request",
"tooltipAcceptContactRequest": "Accept this contact request.", "tooltipAcceptContactRequest": "Accept this contact request.",
"notificationNewMessageFromGroup": "Neue Nachricht in einer Gruppe!", "notificationNewMessageFromGroup": "Neue Nachricht in einer Gruppe!",

View File

@ -1,6 +1,8 @@
{ {
"@@locale": "en", "@@locale": "en",
"@@last_modified": "2021-07-05T21:26:10+02:00", "@@last_modified": "2021-07-07T18:42:50+02:00",
"tooltipRemoveThisQuotedMessage": "Remove quoted message.",
"tooltipReplyToThisMessage": "Reply to this message",
"tooltipRejectContactRequest": "Reject this contact request", "tooltipRejectContactRequest": "Reject this contact request",
"tooltipAcceptContactRequest": "Accept this contact request.", "tooltipAcceptContactRequest": "Accept this contact request.",
"notificationNewMessageFromGroup": "New message in a group!", "notificationNewMessageFromGroup": "New message in a group!",

View File

@ -1,6 +1,8 @@
{ {
"@@locale": "es", "@@locale": "es",
"@@last_modified": "2021-07-05T21:26:10+02:00", "@@last_modified": "2021-07-07T18:42:50+02:00",
"tooltipRemoveThisQuotedMessage": "Remove quoted message.",
"tooltipReplyToThisMessage": "Reply to this message",
"tooltipRejectContactRequest": "Reject this contact request", "tooltipRejectContactRequest": "Reject this contact request",
"tooltipAcceptContactRequest": "Accept this contact request.", "tooltipAcceptContactRequest": "Accept this contact request.",
"notificationNewMessageFromGroup": "New message in a group!", "notificationNewMessageFromGroup": "New message in a group!",

View File

@ -1,6 +1,8 @@
{ {
"@@locale": "fr", "@@locale": "fr",
"@@last_modified": "2021-07-05T21:26:10+02:00", "@@last_modified": "2021-07-07T18:42:50+02:00",
"tooltipRemoveThisQuotedMessage": "Remove quoted message.",
"tooltipReplyToThisMessage": "Reply to this message",
"tooltipRejectContactRequest": "Refuser cette demande de contact", "tooltipRejectContactRequest": "Refuser cette demande de contact",
"tooltipAcceptContactRequest": "Acceptez cette demande de contact.", "tooltipAcceptContactRequest": "Acceptez cette demande de contact.",
"notificationNewMessageFromGroup": "Nouveau message dans un groupe !", "notificationNewMessageFromGroup": "Nouveau message dans un groupe !",

View File

@ -1,6 +1,8 @@
{ {
"@@locale": "it", "@@locale": "it",
"@@last_modified": "2021-07-05T21:26:10+02:00", "@@last_modified": "2021-07-07T18:42:50+02:00",
"tooltipRemoveThisQuotedMessage": "Remove quoted message.",
"tooltipReplyToThisMessage": "Reply to this message",
"tooltipRejectContactRequest": "Rifiuta questa richiesta di contatto", "tooltipRejectContactRequest": "Rifiuta questa richiesta di contatto",
"tooltipAcceptContactRequest": "Accetta questa richiesta di contatto.", "tooltipAcceptContactRequest": "Accetta questa richiesta di contatto.",
"notificationNewMessageFromGroup": "Nuovo messaggio in un gruppo!", "notificationNewMessageFromGroup": "Nuovo messaggio in un gruppo!",

View File

@ -1,6 +1,8 @@
{ {
"@@locale": "pl", "@@locale": "pl",
"@@last_modified": "2021-07-05T21:26:10+02:00", "@@last_modified": "2021-07-07T18:42:50+02:00",
"tooltipRemoveThisQuotedMessage": "Remove quoted message.",
"tooltipReplyToThisMessage": "Reply to this message",
"tooltipRejectContactRequest": "Reject this contact request", "tooltipRejectContactRequest": "Reject this contact request",
"tooltipAcceptContactRequest": "Accept this contact request.", "tooltipAcceptContactRequest": "Accept this contact request.",
"notificationNewMessageFromGroup": "New message in a group!", "notificationNewMessageFromGroup": "New message in a group!",

View File

@ -1,6 +1,8 @@
{ {
"@@locale": "pt", "@@locale": "pt",
"@@last_modified": "2021-06-29T19:15:43+02:00", "@@last_modified": "2021-07-07T18:42:50+02:00",
"tooltipRemoveThisQuotedMessage": "Remove quoted message.",
"tooltipReplyToThisMessage": "Reply to this message",
"tooltipRejectContactRequest": "Reject this contact request", "tooltipRejectContactRequest": "Reject this contact request",
"tooltipAcceptContactRequest": "Accept this contact request.", "tooltipAcceptContactRequest": "Accept this contact request.",
"notificationNewMessageFromGroup": "New message in a group!", "notificationNewMessageFromGroup": "New message in a group!",
@ -42,16 +44,10 @@
"tooltipAddContact": "Add a new contact or conversation", "tooltipAddContact": "Add a new contact or conversation",
"titleManageContacts": "Conversations", "titleManageContacts": "Conversations",
"titleManageServers": "Manage Servers", "titleManageServers": "Manage Servers",
"dateMonthsAgo": "Months Ago",
"dateNever": "Never", "dateNever": "Never",
"dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)",
"dateLastYear": "Last Year", "dateLastYear": "Last Year",
"dateYesterday": "Yesterday", "dateYesterday": "Yesterday",
"dateLastMonth": "Last Month", "dateLastMonth": "Last Month",
"dateWeeksAgo": "Weeks Ago",
"dateDaysAgo": "Days Ago",
"dateHoursAgo": "Hours Ago",
"dateMinutesAgo": "Minutes Ago",
"dateRightNow": "Right Now", "dateRightNow": "Right Now",
"successfullAddedContact": "Successfully added ", "successfullAddedContact": "Successfully added ",
"descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.", "descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",

View File

@ -8,6 +8,17 @@ import 'messages/malformedmessage.dart';
import 'messages/quotedmessage.dart'; import 'messages/quotedmessage.dart';
import 'messages/textmessage.dart'; import 'messages/textmessage.dart';
// Define the overlays
const TextMessageOverlay = 1;
const QuotedMessageOverlay = 10;
const SuggestContactOverlay = 100;
const InviteGroupOverlay = 101;
// Defines the length of the tor v3 onion address. Code using this constant will
// need to updated when we allow multiple different identifiers. At which time
// it will likely be prudent to define a proper Contact wrapper.
const TorV3ContactHandleLength = 56;
abstract class Message { abstract class Message {
MessageMetadata getMetadata(); MessageMetadata getMetadata();
Widget getWidget(BuildContext context); Widget getWidget(BuildContext context);
@ -19,16 +30,22 @@ Future<Message> messageHandler(BuildContext context, String profileOnion, String
var rawMessageEnvelopeFuture = Provider.of<FlwtchState>(context, listen: false).cwtch.GetMessage(profileOnion, contactHandle, index); var rawMessageEnvelopeFuture = Provider.of<FlwtchState>(context, listen: false).cwtch.GetMessage(profileOnion, contactHandle, index);
return rawMessageEnvelopeFuture.then((dynamic rawMessageEnvelope) { return rawMessageEnvelopeFuture.then((dynamic rawMessageEnvelope) {
dynamic messageWrapper = jsonDecode(rawMessageEnvelope); dynamic messageWrapper = jsonDecode(rawMessageEnvelope);
// There are 2 conditions in which this error condition can be met:
// 1. The application == nil, in which case this instance of the UI is already
// broken beyond repair, and will either be replaced by a new version, or requires a complete
// restart.
// 2. This index was incremented and we happened to fetch the timeline prior to the messages inclusion.
// This should be rare as Timeline addition/fetching is mutex protected and Dart itself will pipeline the
// calls to libCwtch-go - however because we use goroutines on the backend there is always a chance that one
// will find itself delayed.
// The second case is recoverable by tail-recursing this future.
if (messageWrapper['Message'] == null || messageWrapper['Message'] == '' || messageWrapper['Message'] == '{}') { if (messageWrapper['Message'] == null || messageWrapper['Message'] == '' || messageWrapper['Message'] == '{}') {
return Future.delayed(Duration(seconds: 2), () { return Future.delayed(Duration(seconds: 2), () {
print("Tail recursive call to messageHandler called. This should be a rare event. If you see multiples of this log over a short period of time please log it as a bug.");
return messageHandler(context, profileOnion, contactHandle, index).then((value) => value); return messageHandler(context, profileOnion, contactHandle, index).then((value) => value);
}); });
} }
dynamic message = jsonDecode(messageWrapper['Message']);
var content = message['d'] as dynamic;
var overlay = int.parse(message['o'].toString());
// Construct the initial metadata // Construct the initial metadata
var timestamp = DateTime.tryParse(messageWrapper['Timestamp'])!; var timestamp = DateTime.tryParse(messageWrapper['Timestamp'])!;
var senderHandle = messageWrapper['PeerID']; var senderHandle = messageWrapper['PeerID'];
@ -36,26 +53,32 @@ Future<Message> messageHandler(BuildContext context, String profileOnion, String
var flags = int.parse(messageWrapper['Flags'].toString(), radix: 2); var flags = int.parse(messageWrapper['Flags'].toString(), radix: 2);
var ackd = messageWrapper['Acknowledged']; var ackd = messageWrapper['Acknowledged'];
var error = messageWrapper['Error'] != null; var error = messageWrapper['Error'] != null;
String? signature; String? signature;
// If this is a group, store the signature // If this is a group, store the signature
if (contactHandle.length == 32) { if (contactHandle.length == 32) {
signature = messageWrapper['Signature']; signature = messageWrapper['Signature'];
} }
var metadata = MessageMetadata(profileOnion, contactHandle, index, timestamp, senderHandle, senderImage, signature, flags, ackd, error); var metadata = MessageMetadata(profileOnion, contactHandle, index, timestamp, senderHandle, senderImage, signature, flags, ackd, error);
switch (overlay) { try {
case 1: dynamic message = jsonDecode(messageWrapper['Message']);
return TextMessage(metadata, content); var content = message['d'] as dynamic;
case 100: var overlay = int.parse(message['o'].toString());
case 101:
return InviteMessage(overlay, metadata, content); switch (overlay) {
case 10: case TextMessageOverlay:
return QuotedMessage(metadata, content); return TextMessage(metadata, content);
default: case SuggestContactOverlay:
// Metadata is valid, content is not.. case InviteGroupOverlay:
return MalformedMessage(metadata); return InviteMessage(overlay, metadata, content);
case QuotedMessageOverlay:
return QuotedMessage(metadata, content);
default:
// Metadata is valid, content is not..
return MalformedMessage(metadata);
}
} catch (e) {
return MalformedMessage(metadata);
} }
}); });
} catch (e) { } catch (e) {

View File

@ -3,7 +3,6 @@ import 'dart:convert';
import 'package:cwtch/models/message.dart'; import 'package:cwtch/models/message.dart';
import 'package:cwtch/widgets/invitationbubble.dart'; import 'package:cwtch/widgets/invitationbubble.dart';
import 'package:cwtch/widgets/malformedbubble.dart'; import 'package:cwtch/widgets/malformedbubble.dart';
import 'package:cwtch/widgets/messagebubble.dart';
import 'package:cwtch/widgets/messagerow.dart'; import 'package:cwtch/widgets/messagerow.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -27,7 +26,7 @@ class InviteMessage extends Message {
String inviteTarget; String inviteTarget;
String inviteNick; String inviteNick;
if (this.content.length == 56) { if (this.content.length == TorV3ContactHandleLength) {
inviteTarget = this.content; inviteTarget = this.content;
var targetContact = Provider.of<ProfileInfoState>(context).contactList.getContact(inviteTarget); var targetContact = Provider.of<ProfileInfoState>(context).contactList.getContact(inviteTarget);
inviteNick = targetContact == null ? this.content : targetContact.nickname; inviteNick = targetContact == null ? this.content : targetContact.nickname;
@ -53,7 +52,7 @@ class InviteMessage extends Message {
String inviteTarget; String inviteTarget;
String inviteNick; String inviteNick;
if (this.content.length == 56) { if (this.content.length == TorV3ContactHandleLength) {
inviteTarget = this.content; inviteTarget = this.content;
var targetContact = Provider.of<ProfileInfoState>(context).contactList.getContact(inviteTarget); var targetContact = Provider.of<ProfileInfoState>(context).contactList.getContact(inviteTarget);
inviteNick = targetContact == null ? this.content : targetContact.nickname; inviteNick = targetContact == null ? this.content : targetContact.nickname;

View File

@ -2,7 +2,6 @@ import 'dart:convert';
import 'package:cwtch/models/message.dart'; import 'package:cwtch/models/message.dart';
import 'package:cwtch/models/messages/malformedmessage.dart'; import 'package:cwtch/models/messages/malformedmessage.dart';
import 'package:cwtch/widgets/messagebubble.dart';
import 'package:cwtch/widgets/messagerow.dart'; import 'package:cwtch/widgets/messagerow.dart';
import 'package:cwtch/widgets/quotedmessage.dart'; import 'package:cwtch/widgets/quotedmessage.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
@ -39,7 +38,7 @@ class QuotedMessage extends Message {
builder: (bcontext, child) { builder: (bcontext, child) {
try { try {
dynamic message = jsonDecode(this.content); dynamic message = jsonDecode(this.content);
return MessageBubble(message["body"]); return Text(message["body"]);
} catch (e) { } catch (e) {
return MalformedMessage(this.metadata).getWidget(context); return MalformedMessage(this.metadata).getWidget(context);
} }
@ -55,10 +54,12 @@ class QuotedMessage extends Message {
Widget getWidget(BuildContext context) { Widget getWidget(BuildContext context) {
try { try {
dynamic message = jsonDecode(this.content); dynamic message = jsonDecode(this.content);
var quotedMessagePotentials = Provider.of<FlwtchState>(context).cwtch.GetMessageByContentHash(metadata.profileOnion, metadata.contactHandle, message["quotedHash"]); var quotedMessagePotentials = Provider.of<FlwtchState>(context).cwtch.GetMessageByContentHash(metadata.profileOnion, metadata.contactHandle, message["quotedHash"]);
int messageIndex = metadata.messageIndex; int messageIndex = metadata.messageIndex;
var quotedMessage = quotedMessagePotentials.then((matchingMessages) { Future<LocallyIndexedMessage?> quotedMessage = quotedMessagePotentials.then((matchingMessages) {
if (matchingMessages == "[]") {
return null;
}
// reverse order the messages from newest to oldest and return the // reverse order the messages from newest to oldest and return the
// first matching message where it's index is less than the index of this // first matching message where it's index is less than the index of this
// message // message
@ -79,15 +80,16 @@ class QuotedMessage extends Message {
builder: (bcontext, child) { builder: (bcontext, child) {
String idx = Provider.of<ContactInfoState>(context).isGroup == true && this.metadata.signature != null ? this.metadata.signature! : this.metadata.messageIndex.toString(); String idx = Provider.of<ContactInfoState>(context).isGroup == true && this.metadata.signature != null ? this.metadata.signature! : this.metadata.messageIndex.toString();
return MessageRow( return MessageRow(
QuotedMessageBubble(message["body"], quotedMessage.then((localIndex) { QuotedMessageBubble(message["body"], quotedMessage.then((LocallyIndexedMessage? localIndex) {
if (localIndex != null) { if (localIndex != null) {
return messageHandler(context, metadata.profileOnion, metadata.contactHandle, localIndex.index); return messageHandler(context, metadata.profileOnion, metadata.contactHandle, localIndex.index);
} }
return Future.value(MalformedMessage(this.metadata)); return MalformedMessage(this.metadata);
})), })),
key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx)); key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx));
}); });
} catch (e) { } catch (e) {
print("Quoted message exception" + e.toString());
return MalformedMessage(this.metadata).getWidget(context); return MalformedMessage(this.metadata).getWidget(context);
} }
} }

View File

@ -9,6 +9,7 @@ import '../../model.dart';
class TextMessage extends Message { class TextMessage extends Message {
final MessageMetadata metadata; final MessageMetadata metadata;
final String content; final String content;
TextMessage(this.metadata, this.content); TextMessage(this.metadata, this.content);
@override @override
@ -16,7 +17,7 @@ class TextMessage extends Message {
return ChangeNotifierProvider.value( return ChangeNotifierProvider.value(
value: this.metadata, value: this.metadata,
builder: (bcontext, child) { builder: (bcontext, child) {
return MessageBubble(this.content); return Text(this.content);
}); });
} }

View File

@ -4,6 +4,7 @@ import 'package:crypto/crypto.dart';
import 'package:cwtch/cwtch_icons_icons.dart'; import 'package:cwtch/cwtch_icons_icons.dart';
import 'package:cwtch/models/message.dart'; import 'package:cwtch/models/message.dart';
import 'package:cwtch/widgets/malformedbubble.dart'; import 'package:cwtch/widgets/malformedbubble.dart';
import 'package:cwtch/widgets/messageloadingbubble.dart';
import 'package:cwtch/widgets/profileimage.dart'; import 'package:cwtch/widgets/profileimage.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -117,7 +118,7 @@ class _MessageViewState extends State<MessageView> {
var digest1 = sha256.convert(bytes1); var digest1 = sha256.convert(bytes1);
var contentHash = base64Encode(digest1.bytes); var contentHash = base64Encode(digest1.bytes);
var quotedMessage = "{\"quotedHash\":\"" + contentHash + "\",\"body\":\"" + ctrlrCompose.value.text + "\"}"; var quotedMessage = "{\"quotedHash\":\"" + contentHash + "\",\"body\":\"" + ctrlrCompose.value.text + "\"}";
ChatMessage cm = new ChatMessage(o: 10, d: quotedMessage); ChatMessage cm = new ChatMessage(o: QuotedMessageOverlay, d: quotedMessage);
Provider.of<FlwtchState>(context, listen: false) Provider.of<FlwtchState>(context, listen: false)
.cwtch .cwtch
.SendMessage(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).onion, jsonEncode(cm)); .SendMessage(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).onion, jsonEncode(cm));
@ -126,7 +127,7 @@ class _MessageViewState extends State<MessageView> {
_sendMessageHelper(); _sendMessageHelper();
}); });
} else { } else {
ChatMessage cm = new ChatMessage(o: 1, d: ctrlrCompose.value.text); ChatMessage cm = new ChatMessage(o: TextMessageOverlay, d: ctrlrCompose.value.text);
Provider.of<FlwtchState>(context, listen: false) Provider.of<FlwtchState>(context, listen: false)
.cwtch .cwtch
.SendMessage(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).onion, jsonEncode(cm)); .SendMessage(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).onion, jsonEncode(cm));
@ -211,9 +212,21 @@ class _MessageViewState extends State<MessageView> {
color: message.getMetadata().senderHandle != Provider.of<AppState>(context).selectedProfile color: message.getMetadata().senderHandle != Provider.of<AppState>(context).selectedProfile
? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor() ? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor()
: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor(), : Provider.of<Settings>(context).theme.messageFromMeBackgroundColor(),
child: message.getPreviewWidget(context)); child: Wrap(runAlignment: WrapAlignment.spaceEvenly, alignment: WrapAlignment.spaceEvenly, runSpacing: 1.0, crossAxisAlignment: WrapCrossAlignment.center, children: [
Center(widthFactor: 1, child: Padding(padding: EdgeInsets.all(10.0), child: Icon(Icons.reply, size: 32))),
Center(widthFactor: 1.0, child: message.getPreviewWidget(context)),
Center(
widthFactor: 1.0,
child: IconButton(
icon: Icon(Icons.highlight_remove),
tooltip: AppLocalizations.of(context)!.tooltipRemoveThisQuotedMessage,
onPressed: () {
Provider.of<AppState>(context, listen: false).selectedIndex = null;
},
))
]));
} else { } else {
return Text(""); return MessageLoadingBubble();
} }
}, },
); );

View File

@ -33,7 +33,7 @@ class InvitationBubbleState extends State<InvitationBubble> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var fromMe = Provider.of<MessageMetadata>(context).senderHandle == Provider.of<ProfileInfoState>(context).onion; var fromMe = Provider.of<MessageMetadata>(context).senderHandle == Provider.of<ProfileInfoState>(context).onion;
var isGroup = widget.overlay == 101; var isGroup = widget.overlay == InviteGroupOverlay;
isAccepted = Provider.of<ProfileInfoState>(context).contactList.getContact(widget.inviteTarget) != null; isAccepted = Provider.of<ProfileInfoState>(context).contactList.getContact(widget.inviteTarget) != null;
var borderRadiousEh = 15.0; var borderRadiousEh = 15.0;
var showGroupInvite = Provider.of<Settings>(context).isExperimentEnabled(TapirGroupsExperiment); var showGroupInvite = Provider.of<Settings>(context).isExperimentEnabled(TapirGroupsExperiment);

View File

@ -28,6 +28,7 @@ class MessageRowState extends State<MessageRow> {
Widget wdgIcons = Visibility( Widget wdgIcons = Visibility(
visible: this.showMenu, visible: this.showMenu,
child: IconButton( child: IconButton(
tooltip: AppLocalizations.of(context)!.tooltipReplyToThisMessage,
onPressed: () { onPressed: () {
Provider.of<AppState>(context, listen: false).selectedIndex = Provider.of<MessageMetadata>(context).messageIndex; Provider.of<AppState>(context, listen: false).selectedIndex = Provider.of<MessageMetadata>(context).messageIndex;
}, },

View File

@ -58,13 +58,21 @@ class QuotedMessageBubbleState extends State<QuotedMessageBubble> {
future: widget.quotedMessage, future: widget.quotedMessage,
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasData) { if (snapshot.hasData) {
var qMessage = (snapshot.data! as Message); try {
// Swap the background color for quoted tweets.. var qMessage = (snapshot.data! as Message);
return Container( // Swap the background color for quoted tweets..
margin: EdgeInsets.all(5), return Container(
padding: EdgeInsets.all(5), margin: EdgeInsets.all(5),
color: fromMe ? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor() : Provider.of<Settings>(context).theme.messageFromMeBackgroundColor(), padding: EdgeInsets.all(5),
child: qMessage.getPreviewWidget(context)); color: fromMe ? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor() : Provider.of<Settings>(context).theme.messageFromMeBackgroundColor(),
child: Wrap(runAlignment: WrapAlignment.spaceEvenly, alignment: WrapAlignment.spaceEvenly, runSpacing: 1.0, crossAxisAlignment: WrapCrossAlignment.center, children: [
Center(widthFactor: 1, child: Padding(padding: EdgeInsets.all(10.0), child: Icon(Icons.reply, size: 32))),
Center(widthFactor: 1.0, child: qMessage.getPreviewWidget(context))
]));
} catch (e) {
print(e);
return MalformedBubble();
}
} else { } else {
// This should be almost instantly resolved, any failure likely means an issue in decoding... // This should be almost instantly resolved, any failure likely means an issue in decoding...
return MessageLoadingBubble(); return MessageLoadingBubble();