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 handle = (a.get("contact") 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" -> {
val profile = (a.get("profile") as? String) ?: ""

View File

@ -1,6 +1,8 @@
{
"@@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",
"tooltipAcceptContactRequest": "Accept this contact request.",
"notificationNewMessageFromGroup": "Neue Nachricht in einer Gruppe!",

View File

@ -1,6 +1,8 @@
{
"@@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",
"tooltipAcceptContactRequest": "Accept this contact request.",
"notificationNewMessageFromGroup": "New message in a group!",

View File

@ -1,6 +1,8 @@
{
"@@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",
"tooltipAcceptContactRequest": "Accept this contact request.",
"notificationNewMessageFromGroup": "New message in a group!",

View File

@ -1,6 +1,8 @@
{
"@@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",
"tooltipAcceptContactRequest": "Acceptez cette demande de contact.",
"notificationNewMessageFromGroup": "Nouveau message dans un groupe !",

View File

@ -1,6 +1,8 @@
{
"@@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",
"tooltipAcceptContactRequest": "Accetta questa richiesta di contatto.",
"notificationNewMessageFromGroup": "Nuovo messaggio in un gruppo!",

View File

@ -1,6 +1,8 @@
{
"@@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",
"tooltipAcceptContactRequest": "Accept this contact request.",
"notificationNewMessageFromGroup": "New message in a group!",

View File

@ -1,6 +1,8 @@
{
"@@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",
"tooltipAcceptContactRequest": "Accept this contact request.",
"notificationNewMessageFromGroup": "New message in a group!",
@ -42,16 +44,10 @@
"tooltipAddContact": "Add a new contact or conversation",
"titleManageContacts": "Conversations",
"titleManageServers": "Manage Servers",
"dateMonthsAgo": "Months Ago",
"dateNever": "Never",
"dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)",
"dateLastYear": "Last Year",
"dateYesterday": "Yesterday",
"dateLastMonth": "Last Month",
"dateWeeksAgo": "Weeks Ago",
"dateDaysAgo": "Days Ago",
"dateHoursAgo": "Hours Ago",
"dateMinutesAgo": "Minutes Ago",
"dateRightNow": "Right Now",
"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.",

View File

@ -8,6 +8,17 @@ import 'messages/malformedmessage.dart';
import 'messages/quotedmessage.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 {
MessageMetadata getMetadata();
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);
return rawMessageEnvelopeFuture.then((dynamic 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'] == '{}') {
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);
});
}
dynamic message = jsonDecode(messageWrapper['Message']);
var content = message['d'] as dynamic;
var overlay = int.parse(message['o'].toString());
// Construct the initial metadata
var timestamp = DateTime.tryParse(messageWrapper['Timestamp'])!;
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 ackd = messageWrapper['Acknowledged'];
var error = messageWrapper['Error'] != null;
String? signature;
// If this is a group, store the signature
if (contactHandle.length == 32) {
signature = messageWrapper['Signature'];
}
var metadata = MessageMetadata(profileOnion, contactHandle, index, timestamp, senderHandle, senderImage, signature, flags, ackd, error);
switch (overlay) {
case 1:
return TextMessage(metadata, content);
case 100:
case 101:
return InviteMessage(overlay, metadata, content);
case 10:
return QuotedMessage(metadata, content);
default:
// Metadata is valid, content is not..
return MalformedMessage(metadata);
try {
dynamic message = jsonDecode(messageWrapper['Message']);
var content = message['d'] as dynamic;
var overlay = int.parse(message['o'].toString());
switch (overlay) {
case TextMessageOverlay:
return TextMessage(metadata, content);
case SuggestContactOverlay:
case InviteGroupOverlay:
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) {

View File

@ -3,7 +3,6 @@ import 'dart:convert';
import 'package:cwtch/models/message.dart';
import 'package:cwtch/widgets/invitationbubble.dart';
import 'package:cwtch/widgets/malformedbubble.dart';
import 'package:cwtch/widgets/messagebubble.dart';
import 'package:cwtch/widgets/messagerow.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';
@ -27,7 +26,7 @@ class InviteMessage extends Message {
String inviteTarget;
String inviteNick;
if (this.content.length == 56) {
if (this.content.length == TorV3ContactHandleLength) {
inviteTarget = this.content;
var targetContact = Provider.of<ProfileInfoState>(context).contactList.getContact(inviteTarget);
inviteNick = targetContact == null ? this.content : targetContact.nickname;
@ -53,7 +52,7 @@ class InviteMessage extends Message {
String inviteTarget;
String inviteNick;
if (this.content.length == 56) {
if (this.content.length == TorV3ContactHandleLength) {
inviteTarget = this.content;
var targetContact = Provider.of<ProfileInfoState>(context).contactList.getContact(inviteTarget);
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/messages/malformedmessage.dart';
import 'package:cwtch/widgets/messagebubble.dart';
import 'package:cwtch/widgets/messagerow.dart';
import 'package:cwtch/widgets/quotedmessage.dart';
import 'package:flutter/widgets.dart';
@ -39,7 +38,7 @@ class QuotedMessage extends Message {
builder: (bcontext, child) {
try {
dynamic message = jsonDecode(this.content);
return MessageBubble(message["body"]);
return Text(message["body"]);
} catch (e) {
return MalformedMessage(this.metadata).getWidget(context);
}
@ -55,10 +54,12 @@ class QuotedMessage extends Message {
Widget getWidget(BuildContext context) {
try {
dynamic message = jsonDecode(this.content);
var quotedMessagePotentials = Provider.of<FlwtchState>(context).cwtch.GetMessageByContentHash(metadata.profileOnion, metadata.contactHandle, message["quotedHash"]);
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
// first matching message where it's index is less than the index of this
// message
@ -79,15 +80,16 @@ class QuotedMessage extends Message {
builder: (bcontext, child) {
String idx = Provider.of<ContactInfoState>(context).isGroup == true && this.metadata.signature != null ? this.metadata.signature! : this.metadata.messageIndex.toString();
return MessageRow(
QuotedMessageBubble(message["body"], quotedMessage.then((localIndex) {
QuotedMessageBubble(message["body"], quotedMessage.then((LocallyIndexedMessage? localIndex) {
if (localIndex != null) {
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));
});
} catch (e) {
print("Quoted message exception" + e.toString());
return MalformedMessage(this.metadata).getWidget(context);
}
}

View File

@ -9,6 +9,7 @@ import '../../model.dart';
class TextMessage extends Message {
final MessageMetadata metadata;
final String content;
TextMessage(this.metadata, this.content);
@override
@ -16,7 +17,7 @@ class TextMessage extends Message {
return ChangeNotifierProvider.value(
value: this.metadata,
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/models/message.dart';
import 'package:cwtch/widgets/malformedbubble.dart';
import 'package:cwtch/widgets/messageloadingbubble.dart';
import 'package:cwtch/widgets/profileimage.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
@ -117,7 +118,7 @@ class _MessageViewState extends State<MessageView> {
var digest1 = sha256.convert(bytes1);
var contentHash = base64Encode(digest1.bytes);
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)
.cwtch
.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();
});
} 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)
.cwtch
.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
? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor()
: 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 {
return Text("");
return MessageLoadingBubble();
}
},
);

View File

@ -33,7 +33,7 @@ class InvitationBubbleState extends State<InvitationBubble> {
@override
Widget build(BuildContext context) {
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;
var borderRadiousEh = 15.0;
var showGroupInvite = Provider.of<Settings>(context).isExperimentEnabled(TapirGroupsExperiment);

View File

@ -28,6 +28,7 @@ class MessageRowState extends State<MessageRow> {
Widget wdgIcons = Visibility(
visible: this.showMenu,
child: IconButton(
tooltip: AppLocalizations.of(context)!.tooltipReplyToThisMessage,
onPressed: () {
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,
builder: (context, snapshot) {
if (snapshot.hasData) {
var qMessage = (snapshot.data! as Message);
// Swap the background color for quoted tweets..
return Container(
margin: EdgeInsets.all(5),
padding: EdgeInsets.all(5),
color: fromMe ? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor() : Provider.of<Settings>(context).theme.messageFromMeBackgroundColor(),
child: qMessage.getPreviewWidget(context));
try {
var qMessage = (snapshot.data! as Message);
// Swap the background color for quoted tweets..
return Container(
margin: EdgeInsets.all(5),
padding: EdgeInsets.all(5),
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 {
// This should be almost instantly resolved, any failure likely means an issue in decoding...
return MessageLoadingBubble();