import 'dart:convert'; import 'package:cwtch/main.dart'; import 'package:cwtch/widgets/malformedbubble.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../model.dart'; import 'package:intl/intl.dart'; import '../settings.dart'; import 'messagebubbledecorations.dart'; class LocallyIndexedMessage { final dynamic message; final int index; LocallyIndexedMessage(this.message, this.index); LocallyIndexedMessage.fromJson(Map json) : message = json['Message'], index = json['LocalIndex']; Map toJson() => { 'Message': message, 'LocalIndex': index, }; } class QuotedMessageBubble extends StatefulWidget { @override QuotedMessageBubbleState createState() => QuotedMessageBubbleState(); } class QuotedMessageBubbleState extends State { FocusNode _focus = FocusNode(); @override Widget build(BuildContext context) { var fromMe = Provider.of(context).senderOnion == Provider.of(context).onion; var prettyDate = ""; var borderRadiousEh = 15.0; var myKey = Provider.of(context).profileOnion + "::" + Provider.of(context).contactHandle + "::" + Provider.of(context).messageIndex.toString(); try { dynamic message = jsonDecode(Provider.of(context).message); var quotedMessagePotentials = Provider.of(context).cwtch.GetMessageByContentHash(Provider.of(context).profileOnion, Provider.of(context).contactHandle, message["quotedHash"]); int messageIndex = Provider.of(context).messageIndex; var quotedMessage = quotedMessagePotentials.then((matchingMessages) { // 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 try { var list = (jsonDecode(matchingMessages) as List).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(context).timestamp != null) { // user-configurable timestamps prolly ideal? #todo DateTime messageDate = Provider.of(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 senderDisplayStr = ""; if (!fromMe && Provider.of(context).senderOnion != null) { ContactInfoState? contact = Provider.of(context).contactList.getContact(Provider.of(context).senderOnion); if (contact != null) { senderDisplayStr = contact.nickname; } else { senderDisplayStr = Provider.of(context).senderOnion; } } var wdgSender = SelectableText(senderDisplayStr, style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of(context).theme.messageFromMeTextColor() : Provider.of(context).theme.messageFromOtherTextColor())); var wdgMessage = SelectableText( (message["body"] ?? "") + '\u202F', key: Key(myKey), focusNode: _focus, style: TextStyle( color: fromMe ? Provider.of(context).theme.messageFromMeTextColor() : Provider.of(context).theme.messageFromOtherTextColor(), ), textAlign: TextAlign.left, textWidthBasis: TextWidthBasis.longestLine, ); 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(context).theme.messageFromOtherBackgroundColor() : Provider.of(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(); } }, ); var wdgDecorations = MessageBubbleDecoration(ackd: Provider.of(context).ackd, errored: Provider.of(context).error, fromMe: fromMe, prettyDate: prettyDate); var error = Provider.of(context).error; return LayoutBuilder(builder: (context, constraints) { return RepaintBoundary( child: Container( child: Container( decoration: BoxDecoration( color: error ? malformedColor : (fromMe ? Provider.of(context).theme.messageFromMeBackgroundColor() : Provider.of(context).theme.messageFromOtherBackgroundColor()), border: Border.all( color: error ? malformedColor : (fromMe ? Provider.of(context).theme.messageFromMeBackgroundColor() : Provider.of(context).theme.messageFromOtherBackgroundColor()), width: 1), borderRadius: BorderRadius.only( topLeft: Radius.circular(borderRadiousEh), topRight: Radius.circular(borderRadiousEh), bottomLeft: fromMe ? Radius.circular(borderRadiousEh) : Radius.zero, bottomRight: fromMe ? Radius.zero : Radius.circular(borderRadiousEh), ), ), child: Padding( padding: EdgeInsets.all(9.0), child: Column( crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: fromMe ? [wdgQuote, wdgMessage, wdgDecorations] : [wdgSender, wdgQuote, wdgMessage, wdgDecorations]))))); }); } catch (e) { return MalformedBubble(); } } }