Quoted Messages Initial Pass

This commit is contained in:
Sarah Jamie Lewis 2021-07-05 12:31:16 -07:00
parent 370a7fd1da
commit b9984a3598
11 changed files with 273 additions and 109 deletions

View File

@ -122,6 +122,12 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
val indexI = a.getInt("index") val indexI = a.getInt("index")
return Result.success(Data.Builder().putString("result", Cwtch.getMessage(profile, handle, indexI.toLong())).build()) return Result.success(Data.Builder().putString("result", Cwtch.getMessage(profile, handle, indexI.toLong())).build())
} }
"GetMessageByContentHash" -> {
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())
}
"UpdateMessageFlags" -> { "UpdateMessageFlags" -> {
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) ?: ""

View File

@ -32,6 +32,8 @@ abstract class Cwtch {
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
Future<dynamic> GetMessage(String profile, String handle, int index); Future<dynamic> GetMessage(String profile, String handle, int index);
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
Future<dynamic> GetMessageByContentHash(String profile, String handle, String contentHash);
// ignore: non_constant_identifier_names
void UpdateMessageFlags(String profile, String handle, int index, int flags); void UpdateMessageFlags(String profile, String handle, int index, int flags);
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
void SendMessage(String profile, String handle, String message); void SendMessage(String profile, String handle, String message);

View File

@ -57,9 +57,9 @@ typedef GetIntFromStrStrFn = int Function(Pointer<Utf8>, int, Pointer<Utf8>, int
typedef get_json_blob_from_str_str_int_function = Pointer<Utf8> Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Int32); typedef get_json_blob_from_str_str_int_function = Pointer<Utf8> Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Int32);
typedef GetJsonBlobFromStrStrIntFn = Pointer<Utf8> Function(Pointer<Utf8>, int, Pointer<Utf8>, int, int); typedef GetJsonBlobFromStrStrIntFn = Pointer<Utf8> Function(Pointer<Utf8>, int, Pointer<Utf8>, int, int);
//func GetMessages(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int, start C.int, end C.int) *C.char { // func c_GetMessagesByContentHash(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int, contenthash_ptr *C.char, contenthash_len C.int) *C.char
typedef get_json_blob_from_str_str_int_int_function = Pointer<Utf8> Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Int32, Int32); typedef get_json_blob_from_str_str_str_function = Pointer<Utf8> Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
typedef GetJsonBlobFromStrStrIntIntFn = Pointer<Utf8> Function(Pointer<Utf8>, int, Pointer<Utf8>, int, int, int); typedef GetJsonBlobFromStrStrStrFn = Pointer<Utf8> Function(Pointer<Utf8>, int, Pointer<Utf8>, int, Pointer<Utf8>, int);
typedef appbus_events_function = Pointer<Utf8> Function(); typedef appbus_events_function = Pointer<Utf8> Function();
typedef AppbusEventsFn = Pointer<Utf8> Function(); typedef AppbusEventsFn = Pointer<Utf8> Function();
@ -397,4 +397,17 @@ class CwtchFfi implements Cwtch {
_receivePort.close(); _receivePort.close();
print("Receive Port Closed"); print("Receive Port Closed");
} }
@override
Future GetMessageByContentHash(String profile, String handle, String contentHash) async {
var getMessagesByContentHashC = library.lookup<NativeFunction<get_json_blob_from_str_str_str_function>>("c_GetMessagesByContentHash");
// ignore: non_constant_identifier_names
final GetMessagesByContentHash = getMessagesByContentHashC.asFunction<GetJsonBlobFromStrStrStrFn>();
final utf8profile = profile.toNativeUtf8();
final utf8handle = handle.toNativeUtf8();
final utf8contentHash = contentHash.toNativeUtf8();
Pointer<Utf8> jsonMessageBytes = GetMessagesByContentHash(utf8profile, utf8profile.length, utf8handle, utf8handle.length, utf8contentHash, utf8contentHash.length);
String jsonMessage = jsonMessageBytes.toDartString();
return jsonMessage;
}
} }

View File

@ -183,4 +183,9 @@ class CwtchGomobile implements Cwtch {
print("gomobile.dart Shutdown"); print("gomobile.dart Shutdown");
cwtchPlatform.invokeMethod("Shutdown", {}); cwtchPlatform.invokeMethod("Shutdown", {});
} }
@override
Future GetMessageByContentHash(String profile, String handle, String contentHash) {
return cwtchPlatform.invokeMethod("GetMessageByContentHash", {"profile": profile, "contact": handle, "contentHash": contentHash});
}
} }

View File

@ -67,6 +67,7 @@ class AppState extends ChangeNotifier {
String appError = ""; String appError = "";
String? _selectedProfile; String? _selectedProfile;
String? _selectedConversation; String? _selectedConversation;
int? _selectedIndex;
void SetCwtchInit() { void SetCwtchInit() {
cwtchInit = true; cwtchInit = true;
@ -90,6 +91,12 @@ class AppState extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
int? get selectedIndex => _selectedIndex;
set selectedIndex(int? newVal) {
this._selectedIndex = newVal;
notifyListeners();
}
bool isLandscape(BuildContext c) => MediaQuery.of(c).size.width > MediaQuery.of(c).size.height; bool isLandscape(BuildContext c) => MediaQuery.of(c).size.width > MediaQuery.of(c).size.height;
} }
@ -457,7 +464,7 @@ class MessageState extends ChangeNotifier {
final String profileOnion; final String profileOnion;
final String contactHandle; final String contactHandle;
final int messageIndex; final int messageIndex;
late String _message; late dynamic _message;
late int _overlay; late int _overlay;
late String _inviteTarget; late String _inviteTarget;
late String _inviteNick; late String _inviteNick;
@ -535,7 +542,7 @@ class MessageState extends ChangeNotifier {
return; return;
} }
dynamic message = jsonDecode(messageWrapper['Message']); dynamic message = jsonDecode(messageWrapper['Message']);
this._message = message['d']; this._message = message['d'] as dynamic;
this._overlay = int.parse(message['o'].toString()); this._overlay = int.parse(message['o'].toString());
this._timestamp = DateTime.tryParse(messageWrapper['Timestamp'])!; this._timestamp = DateTime.tryParse(messageWrapper['Timestamp'])!;
this._senderOnion = messageWrapper['PeerID']; this._senderOnion = messageWrapper['PeerID'];

View File

@ -1,7 +1,8 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:crypto/crypto.dart';
import 'package:cwtch/cwtch_icons_icons.dart'; import 'package:cwtch/cwtch_icons_icons.dart';
import 'package:cwtch/widgets/malformedbubble.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';
@ -104,11 +105,33 @@ class _MessageViewState extends State<MessageView> {
void _sendMessage([String? ignoredParam]) { void _sendMessage([String? ignoredParam]) {
if (ctrlrCompose.value.text.isNotEmpty) { if (ctrlrCompose.value.text.isNotEmpty) {
ChatMessage cm = new ChatMessage(o: 1, d: ctrlrCompose.value.text); if (Provider.of<AppState>(context).selectedConversation != null && Provider.of<AppState>(context).selectedIndex != null) {
Provider.of<FlwtchState>(context, listen: false) Provider.of<FlwtchState>(context)
.cwtch .cwtch
.SendMessage(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).onion, jsonEncode(cm)); .GetMessage(Provider.of<AppState>(context).selectedProfile!, Provider.of<AppState>(context).selectedConversation!, Provider.of<AppState>(context).selectedIndex!).then((data) {
_sendMessageHelper(); try {
var messageWrapper = jsonDecode(data! as String);
var bytes1 = utf8.encode(messageWrapper["PeerID"]+messageWrapper['Message']);
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);
Provider.of<FlwtchState>(context, listen: false)
.cwtch
.SendMessage(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).onion, jsonEncode(cm));
} catch (e) {
}
Provider.of<AppState>(context, listen: false).selectedIndex = null;
_sendMessageHelper();
});
} else {
ChatMessage cm = new ChatMessage(o: 1, 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));
_sendMessageHelper();
}
} }
} }
@ -130,7 +153,7 @@ class _MessageViewState extends State<MessageView> {
} }
Widget _buildComposeBox() { Widget _buildComposeBox() {
return Container( var composeBox = Container(
color: Provider.of<Settings>(context).theme.backgroundMainColor(), color: Provider.of<Settings>(context).theme.backgroundMainColor(),
padding: EdgeInsets.all(2), padding: EdgeInsets.all(2),
margin: EdgeInsets.all(2), margin: EdgeInsets.all(2),
@ -141,52 +164,83 @@ class _MessageViewState extends State<MessageView> {
child: Container( child: Container(
decoration: BoxDecoration(border: Border(top: BorderSide(color: Provider.of<Settings>(context).theme.defaultButtonActiveColor()))), decoration: BoxDecoration(border: Border(top: BorderSide(color: Provider.of<Settings>(context).theme.defaultButtonActiveColor()))),
child: RawKeyboardListener( child: RawKeyboardListener(
focusNode: FocusNode(), focusNode: FocusNode(),
onKey: handleKeyPress, onKey: handleKeyPress,
child: TextFormField( child: TextFormField(
key: Key('txtCompose'), key: Key('txtCompose'),
controller: ctrlrCompose, controller: ctrlrCompose,
focusNode: focusNode, focusNode: focusNode,
autofocus: !Platform.isAndroid, autofocus: !Platform.isAndroid,
textInputAction: TextInputAction.newline, textInputAction: TextInputAction.newline,
keyboardType: TextInputType.multiline, keyboardType: TextInputType.multiline,
minLines: 1, minLines: 1,
maxLines: null, maxLines: null,
onFieldSubmitted: _sendMessage, onFieldSubmitted: _sendMessage,
decoration: InputDecoration( decoration: InputDecoration(
enabledBorder: InputBorder.none, enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none, focusedBorder: InputBorder.none,
enabled: true, enabled: true,
prefixIcon: IconButton( prefixIcon: IconButton(
icon: Icon(CwtchIcons.send_invite, size: 24, color: Provider.of<Settings>(context).theme.mainTextColor()), icon: Icon(CwtchIcons.send_invite, size: 24, color: Provider.of<Settings>(context).theme.mainTextColor()),
tooltip: AppLocalizations.of(context)!.sendInvite, tooltip: AppLocalizations.of(context)!.sendInvite,
enableFeedback: true, enableFeedback: true,
splashColor: Provider.of<Settings>(context).theme.defaultButtonActiveColor(), splashColor: Provider.of<Settings>(context).theme.defaultButtonActiveColor(),
hoverColor: Provider.of<Settings>(context).theme.defaultButtonActiveColor(), hoverColor: Provider.of<Settings>(context).theme.defaultButtonActiveColor(),
onPressed: () => _modalSendInvitation(context)), onPressed: () => _modalSendInvitation(context)),
suffixIcon: IconButton( suffixIcon: IconButton(
icon: Icon(CwtchIcons.send_24px, size: 24, color: Provider.of<Settings>(context).theme.mainTextColor()), icon: Icon(CwtchIcons.send_24px, size: 24, color: Provider.of<Settings>(context).theme.mainTextColor()),
tooltip: AppLocalizations.of(context)!.sendMessage, tooltip: AppLocalizations.of(context)!.sendMessage,
onPressed: _sendMessage, onPressed: _sendMessage,
), ),
)))), )))),
), ),
], ],
), ),
); );
var children;
if (Provider.of<AppState>(context).selectedConversation != null && Provider.of<AppState>(context).selectedIndex != null) {
var quoted = FutureBuilder(
future: Provider.of<FlwtchState>(context)
.cwtch
.GetMessage(Provider.of<AppState>(context).selectedProfile!, Provider.of<AppState>(context).selectedConversation!, Provider.of<AppState>(context).selectedIndex!),
builder: (context, snapshot) {
if (snapshot.hasData) {
try {
var messageWrapper = jsonDecode(snapshot.data! as String);
dynamic message = jsonDecode(messageWrapper['Message']);
return Container(
margin: EdgeInsets.all(5),
padding: EdgeInsets.all(5),
color: messageWrapper['PeerID'] != Provider.of<AppState>(context).selectedProfile
? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor()
: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor(),
child: Text(message["d"]));
} catch (e) {
return MalformedBubble();
}
} else {
return Text("");
}
},
);
children = [quoted, composeBox];
} else {
children = [composeBox];
}
return Column(mainAxisSize: MainAxisSize.min, children: children);
} }
// Send the message if enter is pressed without the shift key... // Send the message if enter is pressed without the shift key...
void handleKeyPress(event) { void handleKeyPress(event) {
var data = event.data as RawKeyEventData; var data = event.data as RawKeyEventData;
if (data.logicalKey == LogicalKeyboardKey.enter && !event.isShiftPressed) { if (data.logicalKey == LogicalKeyboardKey.enter && !event.isShiftPressed) {
final messageWithoutNewLine = ctrlrCompose.value.text.trimRight(); final messageWithoutNewLine = ctrlrCompose.value.text.trimRight();
ctrlrCompose.value = TextEditingValue( ctrlrCompose.value = TextEditingValue(text: messageWithoutNewLine);
text: messageWithoutNewLine _sendMessage();
); }
_sendMessage();
}
} }
void placeHolder() => {}; void placeHolder() => {};

View File

@ -98,6 +98,7 @@ class _ContactRowState extends State<ContactRow> {
Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(contact.onion)!.unreadMessages = 0; Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(contact.onion)!.unreadMessages = 0;
// triggers update in Double/TripleColumnView // triggers update in Double/TripleColumnView
Provider.of<AppState>(context, listen: false).selectedConversation = contact.onion; Provider.of<AppState>(context, listen: false).selectedConversation = contact.onion;
Provider.of<AppState>(context, listen: false).selectedIndex = null;
// if in singlepane mode, push to the stack // if in singlepane mode, push to the stack
var isLandscape = Provider.of<AppState>(context, listen: false).isLandscape(context); var isLandscape = Provider.of<AppState>(context, listen: false).isLandscape(context);
if (Provider.of<Settings>(context, listen: false).uiColumns(isLandscape).length == 1) _pushMessageView(contact.onion); if (Provider.of<Settings>(context, listen: false).uiColumns(isLandscape).length == 1) _pushMessageView(contact.onion);

View File

@ -1,5 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:cwtch/widgets/quotedmessage.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cwtch/widgets/profileimage.dart'; import 'package:cwtch/widgets/profileimage.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -33,14 +34,18 @@ class _MessageRowState extends State<MessageRow> {
Widget wdgBubble = Widget wdgBubble =
Flexible(flex: 3, fit: FlexFit.loose, child: Provider.of<MessageState>(context).loaded == true ? widgetForOverlay(Provider.of<MessageState>(context).overlay) : MessageLoadingBubble()); Flexible(flex: 3, fit: FlexFit.loose, child: Provider.of<MessageState>(context).loaded == true ? widgetForOverlay(Provider.of<MessageState>(context).overlay) : MessageLoadingBubble());
Widget wdgIcons = Icon(Icons.delete_forever_outlined, color: Provider.of<Settings>(context).theme.dropShadowColor()); Widget wdgIcons = IconButton(
onPressed: () {
Provider.of<AppState>(context, listen: false).selectedIndex = Provider.of<MessageState>(context).messageIndex;
},
icon: Icon(Icons.reply, color: Provider.of<Settings>(context).theme.dropShadowColor()));
Widget wdgSpacer = Expanded(child: SizedBox(width: 60, height: 10)); Widget wdgSpacer = Expanded(child: SizedBox(width: 60, height: 10));
var widgetRow = <Widget>[]; var widgetRow = <Widget>[];
if (fromMe) { if (fromMe) {
widgetRow = <Widget>[ widgetRow = <Widget>[
wdgSpacer, wdgSpacer,
//wdgIcons, wdgIcons,
wdgBubble, wdgBubble,
]; ];
} else { } else {
@ -60,7 +65,7 @@ class _MessageRowState extends State<MessageRow> {
widgetRow = <Widget>[ widgetRow = <Widget>[
wdgPortrait, wdgPortrait,
wdgBubble, wdgBubble,
//wdgIcons, wdgIcons,
wdgSpacer, wdgSpacer,
]; ];
} }
@ -75,6 +80,8 @@ class _MessageRowState extends State<MessageRow> {
case 100: case 100:
case 101: case 101:
return InvitationBubble(); return InvitationBubble();
case 10:
return QuotedMessageBubble();
} }
return MalformedBubble(); return MalformedBubble();
} }

View File

@ -1,3 +1,6 @@
import 'dart:convert';
import 'package:cwtch/main.dart';
import 'package:cwtch/widgets/malformedbubble.dart'; import 'package:cwtch/widgets/malformedbubble.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -7,6 +10,22 @@ import 'package:intl/intl.dart';
import '../settings.dart'; import '../settings.dart';
import 'messagebubbledecorations.dart'; import 'messagebubbledecorations.dart';
class LocallyIndexedMessage {
final dynamic message;
final int index;
LocallyIndexedMessage(this.message, this.index);
LocallyIndexedMessage.fromJson(Map<String, dynamic> json)
: message = json['Message'],
index = json['LocalIndex'];
Map<String, dynamic> toJson() => {
'Message': message,
'LocalIndex': index,
};
}
class QuotedMessageBubble extends StatefulWidget { class QuotedMessageBubble extends StatefulWidget {
@override @override
QuotedMessageBubbleState createState() => QuotedMessageBubbleState(); QuotedMessageBubbleState createState() => QuotedMessageBubbleState();
@ -22,68 +41,110 @@ class QuotedMessageBubbleState extends State<QuotedMessageBubble> {
var borderRadiousEh = 15.0; var borderRadiousEh = 15.0;
var myKey = Provider.of<MessageState>(context).profileOnion + "::" + Provider.of<MessageState>(context).contactHandle + "::" + Provider.of<MessageState>(context).messageIndex.toString(); var myKey = Provider.of<MessageState>(context).profileOnion + "::" + Provider.of<MessageState>(context).contactHandle + "::" + Provider.of<MessageState>(context).messageIndex.toString();
if (Provider.of<MessageState>(context).timestamp != null) { try {
// user-configurable timestamps prolly ideal? #todo dynamic message = jsonDecode(Provider.of<MessageState>(context).message);
DateTime messageDate = Provider.of<MessageState>(context).timestamp;
prettyDate = DateFormat.yMd().add_jm().format(messageDate.toLocal());
}
// If the sender is not us, then we want to give them a nickname... var quotedMessagePotentials =
var senderDisplayStr = ""; Provider.of<FlwtchState>(context).cwtch.GetMessageByContentHash(Provider.of<MessageState>(context).profileOnion, Provider.of<MessageState>(context).contactHandle, message["quotedHash"]);
if (!fromMe && Provider.of<MessageState>(context).senderOnion != null) { int messageIndex = Provider.of<MessageState>(context).messageIndex;
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).senderOnion); var quotedMessage = quotedMessagePotentials.then((matchingMessages) {
if (contact != null) { // reverse order the messages from newest to oldest and return the
senderDisplayStr = contact.nickname; // first matching message where it's index is less than the index of this
} else { // message
senderDisplayStr = Provider.of<MessageState>(context).senderOnion; try {
var list = (jsonDecode(matchingMessages) as List<dynamic>).map((data) => LocallyIndexedMessage.fromJson(data)).toList();
LocallyIndexedMessage candidate = list.reversed.firstWhere((element) => messageIndex < element.index, orElse: () {
return list.firstWhere((element) => messageIndex > element.index);
});
return candidate;
} catch (e) {
// Malformed Message will be returned...
}
});
if (Provider.of<MessageState>(context).timestamp != null) {
// user-configurable timestamps prolly ideal? #todo
DateTime messageDate = Provider.of<MessageState>(context).timestamp;
prettyDate = DateFormat.yMd().add_jm().format(messageDate.toLocal());
} }
}
var wdgSender = SelectableText(senderDisplayStr,
style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor()));
var wdgMessage = SelectableText( // If the sender is not us, then we want to give them a nickname...
(Provider.of<MessageState>(context).message ?? "") + '\u202F', var senderDisplayStr = "";
key: Key(myKey), if (!fromMe && Provider.of<MessageState>(context).senderOnion != null) {
focusNode: _focus, ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).senderOnion);
style: TextStyle( if (contact != null) {
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor(), senderDisplayStr = contact.nickname;
), } else {
textAlign: TextAlign.left, senderDisplayStr = Provider.of<MessageState>(context).senderOnion;
textWidthBasis: TextWidthBasis.longestLine, }
); }
var wdgSender = SelectableText(senderDisplayStr,
style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor()));
var wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageState>(context).ackd, errored: Provider.of<MessageState>(context).error, fromMe: fromMe, prettyDate: prettyDate); var wdgMessage = SelectableText(
(message["body"] ?? "") + '\u202F',
key: Key(myKey),
focusNode: _focus,
style: TextStyle(
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor(),
),
textAlign: TextAlign.left,
textWidthBasis: TextWidthBasis.longestLine,
);
var error = Provider.of<MessageState>(context).error; var wdgQuote = FutureBuilder(
future: quotedMessage,
builder: (context, snapshot) {
if (snapshot.hasData) {
var lim = (snapshot.data! as LocallyIndexedMessage);
var limmessage = lim.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: Text(jsonDecode(limmessage)["d"]));
} else {
// This should be almost instantly resolved, any failure likely means an issue in decoding...
return MalformedBubble();
}
},
);
return LayoutBuilder(builder: (context, constraints) { var wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageState>(context).ackd, errored: Provider.of<MessageState>(context).error, fromMe: fromMe, prettyDate: prettyDate);
//print(constraints.toString()+", "+constraints.maxWidth.toString());
return RepaintBoundary( var error = Provider.of<MessageState>(context).error;
child: Container(
child: Container( return LayoutBuilder(builder: (context, constraints) {
decoration: BoxDecoration( return RepaintBoundary(
color: error child: Container(
? malformedColor child: Container(
: (fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor()), decoration: BoxDecoration(
border: Border.all( color: error
color: error ? malformedColor
? malformedColor : (fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor()),
: (fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor()), border: Border.all(
width: 1), color: error
borderRadius: BorderRadius.only( ? malformedColor
topLeft: Radius.circular(borderRadiousEh), : (fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor()),
topRight: Radius.circular(borderRadiousEh), width: 1),
bottomLeft: fromMe ? Radius.circular(borderRadiousEh) : Radius.zero, borderRadius: BorderRadius.only(
bottomRight: fromMe ? Radius.zero : Radius.circular(borderRadiousEh), topLeft: Radius.circular(borderRadiousEh),
topRight: Radius.circular(borderRadiousEh),
bottomLeft: fromMe ? Radius.circular(borderRadiousEh) : Radius.zero,
bottomRight: fromMe ? Radius.zero : Radius.circular(borderRadiousEh),
),
), ),
), child: Padding(
child: Padding( padding: EdgeInsets.all(9.0),
padding: EdgeInsets.all(9.0), child: Column(
child: Column( crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start,
mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start, mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.min, children: fromMe ? [wdgQuote, wdgMessage, wdgDecorations] : [wdgSender, wdgQuote, wdgMessage, wdgDecorations])))));
children: fromMe ? [wdgMessage, wdgDecorations] : [wdgSender, wdgMessage, wdgDecorations]))))); });
}); } catch (e) {
return MalformedBubble();
}
} }
} }

View File

@ -57,6 +57,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.15.0" version: "1.15.0"
crypto:
dependency: "direct main"
description:
name: crypto
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
cupertino_icons: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -35,6 +35,7 @@ dependencies:
ffi: ^1.0.0 ffi: ^1.0.0
path_provider: ^2.0.0 path_provider: ^2.0.0
desktop_notifications: 0.5.0 desktop_notifications: 0.5.0
crypto: 3.0.1
glob: any glob: any
flutter_test: flutter_test: