From ba0a0c2c8571c1cc42cd559db591d2b43b38fceb Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Mon, 20 Mar 2023 13:13:31 -0700 Subject: [PATCH] Save Quoted Messages in Drafts Too --- lib/config.dart | 3 ++- lib/models/appstate.dart | 6 ----- lib/models/contact.dart | 49 +++++++++++++++++++++-------------- lib/models/message_draft.dart | 44 +++++++++++++++++++++++++++++++ lib/views/contactsview.dart | 1 - lib/views/messageview.dart | 25 ++++++++++-------- lib/widgets/messagerow.dart | 8 ++++-- 7 files changed, 95 insertions(+), 41 deletions(-) create mode 100644 lib/models/message_draft.dart diff --git a/lib/config.dart b/lib/config.dart index 3f003064..d5a138cd 100644 --- a/lib/config.dart +++ b/lib/config.dart @@ -10,7 +10,8 @@ class EnvironmentConfig { static void debugLog(String log) { if (EnvironmentConfig.BUILD_VER == dev_version) { - print(log); + String datetime = DateTime.now().toIso8601String(); + print("$datetime $log"); } } } diff --git a/lib/models/appstate.dart b/lib/models/appstate.dart index 0e191578..f543211d 100644 --- a/lib/models/appstate.dart +++ b/lib/models/appstate.dart @@ -53,12 +53,6 @@ class AppState extends ChangeNotifier { notifyListeners(); } - int? get selectedIndex => _selectedIndex; - set selectedIndex(int? newVal) { - this._selectedIndex = newVal; - notifyListeners(); - } - bool get disableFilePicker => _disableFilePicker; set disableFilePicker(bool newVal) { this._disableFilePicker = newVal; diff --git a/lib/models/contact.dart b/lib/models/contact.dart index 85887cef..2ed4901f 100644 --- a/lib/models/contact.dart +++ b/lib/models/contact.dart @@ -1,4 +1,5 @@ import 'package:cwtch/main.dart'; +import 'package:cwtch/models/message_draft.dart'; import 'package:cwtch/models/profile.dart'; import 'package:cwtch/widgets/messagerow.dart'; import 'package:flutter/widgets.dart'; @@ -58,24 +59,28 @@ class ContactInfoState extends ChangeNotifier { int _antispamTickets = 0; String? _acnCircuit; - String? _messageDraft; + MessageDraft _messageDraft = MessageDraft.empty(); - ContactInfoState(this.profileOnion, this.identifier, this.onion, - {nickname = "", - isGroup = false, - accepted = false, - blocked = false, - status = "", - imagePath = "", - defaultImagePath = "", - savePeerHistory = "DeleteHistoryConfirmed", - numMessages = 0, - numUnread = 0, - lastMessageTime, - server, - archived = false, - notificationPolicy = "ConversationNotificationPolicy.Default", - pinned = false}) { + ContactInfoState( + this.profileOnion, + this.identifier, + this.onion, { + nickname = "", + isGroup = false, + accepted = false, + blocked = false, + status = "", + imagePath = "", + defaultImagePath = "", + savePeerHistory = "DeleteHistoryConfirmed", + numMessages = 0, + numUnread = 0, + lastMessageTime, + server, + archived = false, + notificationPolicy = "ConversationNotificationPolicy.Default", + pinned = false, + }) { this._nickname = nickname; this._isGroup = isGroup; this._accepted = accepted; @@ -95,13 +100,13 @@ class ContactInfoState extends ChangeNotifier { keys = Map>(); } - String get nickname => this._nickname + (this._messageDraft != null && this._messageDraft != "" ? "*" : ""); + String get nickname => this._nickname + (this._messageDraft.isNotEmpty() ? "*" : ""); String get savePeerHistory => this._savePeerHistory; String? get acnCircuit => this._acnCircuit; - String? get messageDraft => this._messageDraft; + MessageDraft get messageDraft => this._messageDraft; set antispamTickets(int antispamTickets) { this._antispamTickets = antispamTickets; @@ -163,11 +168,15 @@ class ContactInfoState extends ChangeNotifier { notifyListeners(); } - set messageDraft(String? newVal) { + set messageDraft(MessageDraft newVal) { this._messageDraft = newVal; notifyListeners(); } + void notifyMessageDraftUpdate() { + notifyListeners(); + } + void selected() { this._newMarkerMsgIndex = this._unreadMessages - 1; this._unreadMessages = 0; diff --git a/lib/models/message_draft.dart b/lib/models/message_draft.dart new file mode 100644 index 00000000..c683b48c --- /dev/null +++ b/lib/models/message_draft.dart @@ -0,0 +1,44 @@ +import 'package:flutter/foundation.dart'; + +/// A "MessageDraft" structure that stores information about in-progress message drafts. +/// MessageDraft stores text, quoted replies, and attached images. +/// Only one draft is stored per conversation. +class MessageDraft extends ChangeNotifier { + String? _messageText; + QuotedReference? _quotedReference; + + static MessageDraft empty() { + return MessageDraft(); + } + + bool isNotEmpty() { + return this._messageText != null || this._quotedReference != null; + } + + String? get messageText => _messageText; + + set messageText(String? text) { + this._messageText = text; + notifyListeners(); + } + + set quotedReference(int index) { + this._quotedReference = QuotedReference(index); + notifyListeners(); + } + + QuotedReference? getQuotedMessage() { + return this._quotedReference; + } + + void clearQuotedReference() { + this._quotedReference = null; + notifyListeners(); + } +} + +/// A QuotedReference encapsulates the state of replied-to message. +class QuotedReference { + int index; + QuotedReference(this.index); +} diff --git a/lib/views/contactsview.dart b/lib/views/contactsview.dart index 4fbfc04c..8a4fc1ec 100644 --- a/lib/views/contactsview.dart +++ b/lib/views/contactsview.dart @@ -48,7 +48,6 @@ void selectConversation(BuildContext context, int handle) { // triggers update in Double/TripleColumnView Provider.of(context, listen: false).initialScrollIndex = unread; Provider.of(context, listen: false).selectedConversation = handle; - Provider.of(context, listen: false).selectedIndex = null; Provider.of(context, listen: false).hoveredIndex = -1; // if in singlepane mode, push to the stack var isLandscape = Provider.of(context, listen: false).isLandscape(context); diff --git a/lib/views/messageview.dart b/lib/views/messageview.dart index 5fbc5357..1312078a 100644 --- a/lib/views/messageview.dart +++ b/lib/views/messageview.dart @@ -9,6 +9,7 @@ import 'package:cwtch/models/appstate.dart'; import 'package:cwtch/models/chatmessage.dart'; import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/message.dart'; +import 'package:cwtch/models/message_draft.dart'; import 'package:cwtch/models/messagecache.dart'; import 'package:cwtch/models/messages/quotedmessage.dart'; import 'package:cwtch/models/profile.dart'; @@ -66,7 +67,7 @@ class _MessageViewState extends State { showDown = false; } }); - ctrlrCompose.text = Provider.of(context, listen: false).messageDraft ?? ""; + ctrlrCompose.text = Provider.of(context, listen: false).messageDraft.messageText ?? ""; super.initState(); } @@ -226,7 +227,7 @@ class _MessageViewState extends State { child: MessageList( scrollListener, )), - bottomSheet: showPreview && showMessageFormattingPreview ? _buildPreviewBox() : _buildComposeBox(), + bottomSheet: showPreview && showMessageFormattingPreview ? _buildPreviewBox() : _buildComposeBox(context), )); } @@ -316,10 +317,10 @@ class _MessageViewState extends State { var lengthOk = (isGroup && actualMessageLength < GroupMessageLengthMax) || actualMessageLength <= P2PMessageLengthMax; if (ctrlrCompose.value.text.isNotEmpty && lengthOk) { - if (Provider.of(context, listen: false).selectedConversation != null && Provider.of(context, listen: false).selectedIndex != null) { + if (Provider.of(context, listen: false).selectedConversation != null && Provider.of(context, listen: false).messageDraft.getQuotedMessage() != null) { var conversationId = Provider.of(context, listen: false).selectedConversation!; MessageCache? cache = Provider.of(context, listen: false).contactList.getContact(conversationId)?.messageCache; - ById(Provider.of(context, listen: false).selectedIndex!) + ById(Provider.of(context, listen: false).messageDraft.getQuotedMessage()!.index) .get(Provider.of(context, listen: false).cwtch, Provider.of(context, listen: false).selectedProfile!, conversationId, cache!) .then((MessageInfo? data) { try { @@ -335,7 +336,7 @@ class _MessageViewState extends State { } catch (e) { EnvironmentConfig.debugLog("Exception: reply to message could not be found: " + e.toString()); } - Provider.of(context, listen: false).selectedIndex = null; + Provider.of(context, listen: false).messageDraft.clearQuotedReference(); }); } else { ChatMessage cm = new ChatMessage(o: TextMessageOverlay, d: ctrlrCompose.value.text); @@ -370,7 +371,7 @@ class _MessageViewState extends State { // At this point we have decided to send the text to the backend, failure is still possible // but it will show as an error-ed message, as such the draft can be purged. - Provider.of(context, listen: false).messageDraft = null; + Provider.of(context, listen: false).messageDraft = MessageDraft.empty(); ctrlrCompose.clear(); var profileOnion = Provider.of(context, listen: false).profileOnion; @@ -456,7 +457,7 @@ class _MessageViewState extends State { color: Provider.of(context).theme.backgroundMainColor, child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [composeBox])); } - Widget _buildComposeBox() { + Widget _buildComposeBox(BuildContext context) { bool isOffline = Provider.of(context).isOnline() == false; bool isGroup = Provider.of(context).isGroup; var showToolbar = Provider.of(context).isExperimentEnabled(FormattingExperiment); @@ -589,7 +590,7 @@ class _MessageViewState extends State { enabled: true, // always allow editing... onChanged: (String x) { - Provider.of(context, listen: false).messageDraft = x; + Provider.of(context, listen: false).messageDraft.messageText = x; setState(() { // we need to force a rerender here to update the max length count }); @@ -625,9 +626,10 @@ class _MessageViewState extends State { Container(color: Provider.of(context).theme.backgroundMainColor, padding: EdgeInsets.all(2), margin: EdgeInsets.all(2), height: 164, child: Column(children: textEditChildren)); var children; - if (Provider.of(context).selectedConversation != null && Provider.of(context).selectedIndex != null) { + if (Provider.of(context).selectedConversation != null && Provider.of(context).messageDraft.getQuotedMessage() != null) { var quoted = FutureBuilder( - future: messageHandler(context, Provider.of(context).selectedProfile!, Provider.of(context).selectedConversation!, ById(Provider.of(context).selectedIndex!)), + future: messageHandler(context, Provider.of(context).selectedProfile!, Provider.of(context).selectedConversation!, + ById(Provider.of(context).messageDraft.getQuotedMessage()!.index)), builder: (context, snapshot) { if (snapshot.hasData) { var message = snapshot.data! as Message; @@ -669,7 +671,8 @@ class _MessageViewState extends State { splashRadius: Material.defaultSplashRadius / 2, tooltip: AppLocalizations.of(context)!.tooltipRemoveThisQuotedMessage, onPressed: () { - Provider.of(context, listen: false).selectedIndex = null; + Provider.of(context, listen: false).messageDraft.clearQuotedReference(); + setState(() {}); }, )), ]), diff --git a/lib/widgets/messagerow.dart b/lib/widgets/messagerow.dart index 6bd6475d..53c4c55f 100644 --- a/lib/widgets/messagerow.dart +++ b/lib/widgets/messagerow.dart @@ -89,7 +89,9 @@ class MessageRowState extends State with SingleTickerProviderStateMi tooltip: AppLocalizations.of(context)!.tooltipReplyToThisMessage, splashRadius: Material.defaultSplashRadius / 2, onPressed: () { - Provider.of(context, listen: false).selectedIndex = Provider.of(context, listen: false).messageID; + Provider.of(context, listen: false).messageDraft.quotedReference = Provider.of(context, listen: false).messageID; + Provider.of(context, listen: false).notifyMessageDraftUpdate(); + setState(() {}); }, icon: Icon(Icons.reply, color: Provider.of(context).theme.dropShadowColor))); @@ -243,7 +245,9 @@ class MessageRowState extends State with SingleTickerProviderStateMi }, onPanEnd: (details) { _runAnimation(details.velocity.pixelsPerSecond, size); - Provider.of(context, listen: false).selectedIndex = Provider.of(context, listen: false).messageID; + Provider.of(context, listen: false).messageDraft.quotedReference = Provider.of(context, listen: false).messageID; + Provider.of(context, listen: false).notifyMessageDraftUpdate(); + setState(() {}); }, onLongPress: () async { modalShowReplies(context, AppLocalizations.of(context)!.headingReplies, AppLocalizations.of(context)!.messageNoReplies, settings, pis, cis, borderColor, cache, messageID);