Save Quoted Messages in Drafts Too
continuous-integration/drone/pr Build is running Details

This commit is contained in:
Sarah Jamie Lewis 2023-03-20 13:13:31 -07:00 committed by Gitea
parent a2d36e62ff
commit ba0a0c2c85
7 changed files with 95 additions and 41 deletions

View File

@ -10,7 +10,8 @@ class EnvironmentConfig {
static void debugLog(String log) { static void debugLog(String log) {
if (EnvironmentConfig.BUILD_VER == dev_version) { if (EnvironmentConfig.BUILD_VER == dev_version) {
print(log); String datetime = DateTime.now().toIso8601String();
print("$datetime $log");
} }
} }
} }

View File

@ -53,12 +53,6 @@ class AppState extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
int? get selectedIndex => _selectedIndex;
set selectedIndex(int? newVal) {
this._selectedIndex = newVal;
notifyListeners();
}
bool get disableFilePicker => _disableFilePicker; bool get disableFilePicker => _disableFilePicker;
set disableFilePicker(bool newVal) { set disableFilePicker(bool newVal) {
this._disableFilePicker = newVal; this._disableFilePicker = newVal;

View File

@ -1,4 +1,5 @@
import 'package:cwtch/main.dart'; import 'package:cwtch/main.dart';
import 'package:cwtch/models/message_draft.dart';
import 'package:cwtch/models/profile.dart'; import 'package:cwtch/models/profile.dart';
import 'package:cwtch/widgets/messagerow.dart'; import 'package:cwtch/widgets/messagerow.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
@ -58,24 +59,28 @@ class ContactInfoState extends ChangeNotifier {
int _antispamTickets = 0; int _antispamTickets = 0;
String? _acnCircuit; String? _acnCircuit;
String? _messageDraft; MessageDraft _messageDraft = MessageDraft.empty();
ContactInfoState(this.profileOnion, this.identifier, this.onion, ContactInfoState(
{nickname = "", this.profileOnion,
isGroup = false, this.identifier,
accepted = false, this.onion, {
blocked = false, nickname = "",
status = "", isGroup = false,
imagePath = "", accepted = false,
defaultImagePath = "", blocked = false,
savePeerHistory = "DeleteHistoryConfirmed", status = "",
numMessages = 0, imagePath = "",
numUnread = 0, defaultImagePath = "",
lastMessageTime, savePeerHistory = "DeleteHistoryConfirmed",
server, numMessages = 0,
archived = false, numUnread = 0,
notificationPolicy = "ConversationNotificationPolicy.Default", lastMessageTime,
pinned = false}) { server,
archived = false,
notificationPolicy = "ConversationNotificationPolicy.Default",
pinned = false,
}) {
this._nickname = nickname; this._nickname = nickname;
this._isGroup = isGroup; this._isGroup = isGroup;
this._accepted = accepted; this._accepted = accepted;
@ -95,13 +100,13 @@ class ContactInfoState extends ChangeNotifier {
keys = Map<String, GlobalKey<MessageRowState>>(); keys = Map<String, GlobalKey<MessageRowState>>();
} }
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 savePeerHistory => this._savePeerHistory;
String? get acnCircuit => this._acnCircuit; String? get acnCircuit => this._acnCircuit;
String? get messageDraft => this._messageDraft; MessageDraft get messageDraft => this._messageDraft;
set antispamTickets(int antispamTickets) { set antispamTickets(int antispamTickets) {
this._antispamTickets = antispamTickets; this._antispamTickets = antispamTickets;
@ -163,11 +168,15 @@ class ContactInfoState extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
set messageDraft(String? newVal) { set messageDraft(MessageDraft newVal) {
this._messageDraft = newVal; this._messageDraft = newVal;
notifyListeners(); notifyListeners();
} }
void notifyMessageDraftUpdate() {
notifyListeners();
}
void selected() { void selected() {
this._newMarkerMsgIndex = this._unreadMessages - 1; this._newMarkerMsgIndex = this._unreadMessages - 1;
this._unreadMessages = 0; this._unreadMessages = 0;

View File

@ -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);
}

View File

@ -48,7 +48,6 @@ void selectConversation(BuildContext context, int handle) {
// triggers update in Double/TripleColumnView // triggers update in Double/TripleColumnView
Provider.of<AppState>(context, listen: false).initialScrollIndex = unread; Provider.of<AppState>(context, listen: false).initialScrollIndex = unread;
Provider.of<AppState>(context, listen: false).selectedConversation = handle; Provider.of<AppState>(context, listen: false).selectedConversation = handle;
Provider.of<AppState>(context, listen: false).selectedIndex = null;
Provider.of<AppState>(context, listen: false).hoveredIndex = -1; Provider.of<AppState>(context, listen: false).hoveredIndex = -1;
// 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);

View File

@ -9,6 +9,7 @@ import 'package:cwtch/models/appstate.dart';
import 'package:cwtch/models/chatmessage.dart'; import 'package:cwtch/models/chatmessage.dart';
import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/contact.dart';
import 'package:cwtch/models/message.dart'; import 'package:cwtch/models/message.dart';
import 'package:cwtch/models/message_draft.dart';
import 'package:cwtch/models/messagecache.dart'; import 'package:cwtch/models/messagecache.dart';
import 'package:cwtch/models/messages/quotedmessage.dart'; import 'package:cwtch/models/messages/quotedmessage.dart';
import 'package:cwtch/models/profile.dart'; import 'package:cwtch/models/profile.dart';
@ -66,7 +67,7 @@ class _MessageViewState extends State<MessageView> {
showDown = false; showDown = false;
} }
}); });
ctrlrCompose.text = Provider.of<ContactInfoState>(context, listen: false).messageDraft ?? ""; ctrlrCompose.text = Provider.of<ContactInfoState>(context, listen: false).messageDraft.messageText ?? "";
super.initState(); super.initState();
} }
@ -226,7 +227,7 @@ class _MessageViewState extends State<MessageView> {
child: MessageList( child: MessageList(
scrollListener, scrollListener,
)), )),
bottomSheet: showPreview && showMessageFormattingPreview ? _buildPreviewBox() : _buildComposeBox(), bottomSheet: showPreview && showMessageFormattingPreview ? _buildPreviewBox() : _buildComposeBox(context),
)); ));
} }
@ -316,10 +317,10 @@ class _MessageViewState extends State<MessageView> {
var lengthOk = (isGroup && actualMessageLength < GroupMessageLengthMax) || actualMessageLength <= P2PMessageLengthMax; var lengthOk = (isGroup && actualMessageLength < GroupMessageLengthMax) || actualMessageLength <= P2PMessageLengthMax;
if (ctrlrCompose.value.text.isNotEmpty && lengthOk) { if (ctrlrCompose.value.text.isNotEmpty && lengthOk) {
if (Provider.of<AppState>(context, listen: false).selectedConversation != null && Provider.of<AppState>(context, listen: false).selectedIndex != null) { if (Provider.of<AppState>(context, listen: false).selectedConversation != null && Provider.of<ContactInfoState>(context, listen: false).messageDraft.getQuotedMessage() != null) {
var conversationId = Provider.of<AppState>(context, listen: false).selectedConversation!; var conversationId = Provider.of<AppState>(context, listen: false).selectedConversation!;
MessageCache? cache = Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(conversationId)?.messageCache; MessageCache? cache = Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(conversationId)?.messageCache;
ById(Provider.of<AppState>(context, listen: false).selectedIndex!) ById(Provider.of<ContactInfoState>(context, listen: false).messageDraft.getQuotedMessage()!.index)
.get(Provider.of<FlwtchState>(context, listen: false).cwtch, Provider.of<AppState>(context, listen: false).selectedProfile!, conversationId, cache!) .get(Provider.of<FlwtchState>(context, listen: false).cwtch, Provider.of<AppState>(context, listen: false).selectedProfile!, conversationId, cache!)
.then((MessageInfo? data) { .then((MessageInfo? data) {
try { try {
@ -335,7 +336,7 @@ class _MessageViewState extends State<MessageView> {
} catch (e) { } catch (e) {
EnvironmentConfig.debugLog("Exception: reply to message could not be found: " + e.toString()); EnvironmentConfig.debugLog("Exception: reply to message could not be found: " + e.toString());
} }
Provider.of<AppState>(context, listen: false).selectedIndex = null; Provider.of<ContactInfoState>(context, listen: false).messageDraft.clearQuotedReference();
}); });
} else { } else {
ChatMessage cm = new ChatMessage(o: TextMessageOverlay, d: ctrlrCompose.value.text); ChatMessage cm = new ChatMessage(o: TextMessageOverlay, d: ctrlrCompose.value.text);
@ -370,7 +371,7 @@ class _MessageViewState extends State<MessageView> {
// At this point we have decided to send the text to the backend, failure is still possible // 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. // but it will show as an error-ed message, as such the draft can be purged.
Provider.of<ContactInfoState>(context, listen: false).messageDraft = null; Provider.of<ContactInfoState>(context, listen: false).messageDraft = MessageDraft.empty();
ctrlrCompose.clear(); ctrlrCompose.clear();
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion; var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
@ -456,7 +457,7 @@ class _MessageViewState extends State<MessageView> {
color: Provider.of<Settings>(context).theme.backgroundMainColor, child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [composeBox])); color: Provider.of<Settings>(context).theme.backgroundMainColor, child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [composeBox]));
} }
Widget _buildComposeBox() { Widget _buildComposeBox(BuildContext context) {
bool isOffline = Provider.of<ContactInfoState>(context).isOnline() == false; bool isOffline = Provider.of<ContactInfoState>(context).isOnline() == false;
bool isGroup = Provider.of<ContactInfoState>(context).isGroup; bool isGroup = Provider.of<ContactInfoState>(context).isGroup;
var showToolbar = Provider.of<Settings>(context).isExperimentEnabled(FormattingExperiment); var showToolbar = Provider.of<Settings>(context).isExperimentEnabled(FormattingExperiment);
@ -589,7 +590,7 @@ class _MessageViewState extends State<MessageView> {
enabled: true, // always allow editing... enabled: true, // always allow editing...
onChanged: (String x) { onChanged: (String x) {
Provider.of<ContactInfoState>(context, listen: false).messageDraft = x; Provider.of<ContactInfoState>(context, listen: false).messageDraft.messageText = x;
setState(() { setState(() {
// we need to force a rerender here to update the max length count // we need to force a rerender here to update the max length count
}); });
@ -625,9 +626,10 @@ class _MessageViewState extends State<MessageView> {
Container(color: Provider.of<Settings>(context).theme.backgroundMainColor, padding: EdgeInsets.all(2), margin: EdgeInsets.all(2), height: 164, child: Column(children: textEditChildren)); Container(color: Provider.of<Settings>(context).theme.backgroundMainColor, padding: EdgeInsets.all(2), margin: EdgeInsets.all(2), height: 164, child: Column(children: textEditChildren));
var children; var children;
if (Provider.of<AppState>(context).selectedConversation != null && Provider.of<AppState>(context).selectedIndex != null) { if (Provider.of<AppState>(context).selectedConversation != null && Provider.of<ContactInfoState>(context).messageDraft.getQuotedMessage() != null) {
var quoted = FutureBuilder( var quoted = FutureBuilder(
future: messageHandler(context, Provider.of<AppState>(context).selectedProfile!, Provider.of<AppState>(context).selectedConversation!, ById(Provider.of<AppState>(context).selectedIndex!)), future: messageHandler(context, Provider.of<AppState>(context).selectedProfile!, Provider.of<AppState>(context).selectedConversation!,
ById(Provider.of<ContactInfoState>(context).messageDraft.getQuotedMessage()!.index)),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasData) { if (snapshot.hasData) {
var message = snapshot.data! as Message; var message = snapshot.data! as Message;
@ -669,7 +671,8 @@ class _MessageViewState extends State<MessageView> {
splashRadius: Material.defaultSplashRadius / 2, splashRadius: Material.defaultSplashRadius / 2,
tooltip: AppLocalizations.of(context)!.tooltipRemoveThisQuotedMessage, tooltip: AppLocalizations.of(context)!.tooltipRemoveThisQuotedMessage,
onPressed: () { onPressed: () {
Provider.of<AppState>(context, listen: false).selectedIndex = null; Provider.of<ContactInfoState>(context, listen: false).messageDraft.clearQuotedReference();
setState(() {});
}, },
)), )),
]), ]),

View File

@ -89,7 +89,9 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
tooltip: AppLocalizations.of(context)!.tooltipReplyToThisMessage, tooltip: AppLocalizations.of(context)!.tooltipReplyToThisMessage,
splashRadius: Material.defaultSplashRadius / 2, splashRadius: Material.defaultSplashRadius / 2,
onPressed: () { onPressed: () {
Provider.of<AppState>(context, listen: false).selectedIndex = Provider.of<MessageMetadata>(context, listen: false).messageID; Provider.of<ContactInfoState>(context, listen: false).messageDraft.quotedReference = Provider.of<MessageMetadata>(context, listen: false).messageID;
Provider.of<ContactInfoState>(context, listen: false).notifyMessageDraftUpdate();
setState(() {});
}, },
icon: Icon(Icons.reply, color: Provider.of<Settings>(context).theme.dropShadowColor))); icon: Icon(Icons.reply, color: Provider.of<Settings>(context).theme.dropShadowColor)));
@ -243,7 +245,9 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
}, },
onPanEnd: (details) { onPanEnd: (details) {
_runAnimation(details.velocity.pixelsPerSecond, size); _runAnimation(details.velocity.pixelsPerSecond, size);
Provider.of<AppState>(context, listen: false).selectedIndex = Provider.of<MessageMetadata>(context, listen: false).messageID; Provider.of<ContactInfoState>(context, listen: false).messageDraft.quotedReference = Provider.of<MessageMetadata>(context, listen: false).messageID;
Provider.of<ContactInfoState>(context, listen: false).notifyMessageDraftUpdate();
setState(() {});
}, },
onLongPress: () async { onLongPress: () async {
modalShowReplies(context, AppLocalizations.of(context)!.headingReplies, AppLocalizations.of(context)!.messageNoReplies, settings, pis, cis, borderColor, cache, messageID); modalShowReplies(context, AppLocalizations.of(context)!.headingReplies, AppLocalizations.of(context)!.messageNoReplies, settings, pis, cis, borderColor, cache, messageID);