refactor: improvements to fake message display, allow users to send more than one fake message at a time (#2925)
This commit is contained in:
parent
583479873d
commit
0c4597226f
8 changed files with 102 additions and 57 deletions
|
|
@ -3,6 +3,7 @@
|
|||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
@ -39,6 +40,7 @@ import 'package:fluffychat/pangea/choreographer/enums/edit_type.dart';
|
|||
import 'package:fluffychat/pangea/choreographer/models/choreo_record.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/igc/message_analytics_feedback.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/igc/pangea_text_controller.dart';
|
||||
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/firebase_analytics.dart';
|
||||
|
|
@ -515,7 +517,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
// #Pangea
|
||||
// If fake event was sent, don't animate in the next event.
|
||||
// It makes the replacement of the fake event jumpy.
|
||||
if (_fakeEventID != null) {
|
||||
if (_fakeEventIDs.isNotEmpty) {
|
||||
animateInEventIndex = null;
|
||||
return;
|
||||
}
|
||||
|
|
@ -687,7 +689,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
MatrixState.pAnyState.closeAllOverlays(force: true);
|
||||
showToolbarStream.close();
|
||||
stopMediaStream.close();
|
||||
hideTextController.dispose();
|
||||
_levelSubscription?.cancel();
|
||||
_analyticsSubscription?.cancel();
|
||||
_router.routeInformationProvider.removeListener(_onRouteChanged);
|
||||
|
|
@ -720,10 +721,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
|
||||
// TextEditingController sendController = TextEditingController();
|
||||
PangeaTextController get sendController => choreographer.textController;
|
||||
|
||||
/// used to obscure text in text field after sending fake message without
|
||||
/// changing the actual text in the sendController
|
||||
final TextEditingController hideTextController = TextEditingController();
|
||||
// #Pangea
|
||||
|
||||
void setSendingClient(Client c) {
|
||||
|
|
@ -758,26 +755,47 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
pangeaEditingEvent = null;
|
||||
}
|
||||
|
||||
String? _fakeEventID;
|
||||
bool get obscureText => _fakeEventID != null;
|
||||
final List<String> _fakeEventIDs = [];
|
||||
bool get obscureText => _fakeEventIDs.isNotEmpty;
|
||||
|
||||
/// Add a fake event to the timeline to visually indicate that a message is being sent.
|
||||
/// Used when tokenizing after message send, specifically because tokenization for some
|
||||
/// languages takes some time.
|
||||
void sendFakeMessage() {
|
||||
String? sendFakeMessage() {
|
||||
if (sendController.text.trim().isEmpty) return null;
|
||||
|
||||
final eventID = room.sendFakeMessage(
|
||||
text: sendController.text,
|
||||
inReplyTo: replyEvent,
|
||||
editEventId: editEvent?.eventId,
|
||||
);
|
||||
setState(() => _fakeEventID = eventID);
|
||||
sendController.setSystemText("", EditType.other);
|
||||
setState(() => _fakeEventIDs.add(eventID));
|
||||
|
||||
// wait for the next event to come through before clearing any fake event,
|
||||
// to make the replacement look smooth
|
||||
room.client.onTimelineEvent.stream
|
||||
.firstWhere((event) => event.content[ModelKey.tempEventId] == eventID)
|
||||
.then(
|
||||
(_) => clearFakeEvent(eventID),
|
||||
);
|
||||
|
||||
return eventID;
|
||||
}
|
||||
|
||||
void clearFakeEvent() {
|
||||
if (_fakeEventID == null) return;
|
||||
timeline?.events.removeWhere((e) => e.eventId == _fakeEventID);
|
||||
void clearFakeEvent(String? eventId) {
|
||||
if (eventId == null) return;
|
||||
|
||||
final inTimeline = timeline != null &&
|
||||
timeline!.events.any(
|
||||
(e) => e.eventId == eventId,
|
||||
);
|
||||
|
||||
if (!inTimeline) return;
|
||||
timeline?.events.removeWhere((e) => e.eventId == eventId);
|
||||
|
||||
setState(() {
|
||||
_fakeEventID = null;
|
||||
_fakeEventIDs.remove(eventId);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -786,20 +804,26 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
// but for choero, the tx id is generated before the message send.
|
||||
// Also, adding PangeaMessageData
|
||||
Future<void> send({
|
||||
required String message,
|
||||
PangeaRepresentation? originalSent,
|
||||
PangeaRepresentation? originalWritten,
|
||||
PangeaMessageTokens? tokensSent,
|
||||
PangeaMessageTokens? tokensWritten,
|
||||
ChoreoRecord? choreo,
|
||||
String? tempEventId,
|
||||
}) async {
|
||||
if (message.trim().isEmpty) return;
|
||||
// if (sendController.text.trim().isEmpty) return;
|
||||
// Pangea#
|
||||
if (sendController.text.trim().isEmpty) return;
|
||||
_storeInputTimeoutTimer?.cancel();
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
prefs.remove('draft_$roomId');
|
||||
var parseCommands = true;
|
||||
|
||||
final commandMatch = RegExp(r'^\/(\w+)').firstMatch(sendController.text);
|
||||
// #Pangea
|
||||
// final commandMatch = RegExp(r'^\/(\w+)').firstMatch(sendController.text);
|
||||
final commandMatch = RegExp(r'^\/(\w+)').firstMatch(message);
|
||||
// Pangea#
|
||||
if (commandMatch != null &&
|
||||
!sendingClient.commands.keys.contains(commandMatch[1]!.toLowerCase())) {
|
||||
final l10n = L10n.of(context);
|
||||
|
|
@ -810,7 +834,13 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
okLabel: l10n.sendAsText,
|
||||
cancelLabel: l10n.cancel,
|
||||
);
|
||||
if (dialogResult == OkCancelResult.cancel) return;
|
||||
// #Pangea
|
||||
// if (dialogResult == OkCancelResult.cancel) return;
|
||||
if (dialogResult == OkCancelResult.cancel) {
|
||||
clearFakeEvent(tempEventId);
|
||||
return;
|
||||
}
|
||||
// Pangea#
|
||||
parseCommands = false;
|
||||
}
|
||||
|
||||
|
|
@ -822,15 +852,20 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
// editEventId: editEvent?.eventId,
|
||||
// parseCommands: parseCommands,
|
||||
// );
|
||||
final previousEdit = editEvent;
|
||||
|
||||
// wait for the next event to come through before clearing any fake event,
|
||||
// to make the replacement look smooth
|
||||
room.client.onTimelineEvent.stream.first.then((_) => clearFakeEvent());
|
||||
// If the message and the sendController text don't match, it's possible
|
||||
// that there was a delay in tokenization before send, and the user started
|
||||
// typing a new message. We don't want to erase that, so only reset the input
|
||||
// bar text if the message is the same as the sendController text.
|
||||
if (message == sendController.text) {
|
||||
sendController.setSystemText("", EditType.other);
|
||||
}
|
||||
|
||||
final previousEdit = editEvent;
|
||||
|
||||
room
|
||||
.pangeaSendTextEvent(
|
||||
sendController.text,
|
||||
message,
|
||||
inReplyTo: replyEvent,
|
||||
editEventId: editEvent?.eventId,
|
||||
parseCommands: parseCommands,
|
||||
|
|
@ -839,6 +874,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
tokensSent: tokensSent,
|
||||
tokensWritten: tokensWritten,
|
||||
choreo: choreo,
|
||||
tempEventId: tempEventId,
|
||||
)
|
||||
.then(
|
||||
(String? msgEventId) async {
|
||||
|
|
@ -915,7 +951,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
s: StackTrace.current,
|
||||
data: {
|
||||
'roomId': roomId,
|
||||
'text': sendController.text,
|
||||
'text': message,
|
||||
'inReplyTo': replyEvent?.eventId,
|
||||
'editEventId': editEvent?.eventId,
|
||||
},
|
||||
|
|
@ -924,7 +960,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
},
|
||||
).catchError((err, s) {
|
||||
clearFakeEvent();
|
||||
clearFakeEvent(tempEventId);
|
||||
if (err is EventTooLarge) {
|
||||
showAdaptiveDialog(
|
||||
context: context,
|
||||
|
|
@ -937,22 +973,21 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
s: s,
|
||||
data: {
|
||||
'roomId': roomId,
|
||||
'text': sendController.text,
|
||||
'text': message,
|
||||
'inReplyTo': replyEvent?.eventId,
|
||||
'editEventId': editEvent?.eventId,
|
||||
},
|
||||
);
|
||||
});
|
||||
// sendController.value = TextEditingValue(
|
||||
// text: pendingText,
|
||||
// selection: const TextSelection.collapsed(offset: 0),
|
||||
// );
|
||||
// Pangea#
|
||||
sendController.value = TextEditingValue(
|
||||
text: pendingText,
|
||||
selection: const TextSelection.collapsed(offset: 0),
|
||||
);
|
||||
|
||||
setState(() {
|
||||
// #Pangea
|
||||
// sendController.text = pendingText;
|
||||
sendController.setSystemText(pendingText, EditType.other);
|
||||
// Pangea#
|
||||
_inputTextIsEmpty = pendingText.isEmpty;
|
||||
replyEvent = null;
|
||||
|
|
|
|||
|
|
@ -320,7 +320,12 @@ class ChatInputRow extends StatelessWidget {
|
|||
)
|
||||
: FloatingActionButton.small(
|
||||
tooltip: L10n.of(context).send,
|
||||
onPressed: controller.send,
|
||||
// #Pangea
|
||||
// onPressed: controller.send,
|
||||
onPressed: () => controller.send(
|
||||
message: controller.sendController.text,
|
||||
),
|
||||
// Pangea#
|
||||
elevation: 0,
|
||||
heroTag: null,
|
||||
shape: RoundedRectangleBorder(
|
||||
|
|
|
|||
|
|
@ -429,16 +429,7 @@ class InputBar extends StatelessWidget {
|
|||
direction: VerticalDirection.up,
|
||||
hideOnEmpty: true,
|
||||
hideOnLoading: true,
|
||||
// #Pangea
|
||||
// if should obscure text (to make it looks that a message has been sent after sending fake message),
|
||||
// use hideTextController
|
||||
|
||||
// controller: controller,
|
||||
controller:
|
||||
(controller?.choreographer.chatController.obscureText) ?? false
|
||||
? controller?.choreographer.chatController.hideTextController
|
||||
: controller,
|
||||
// Pangea#
|
||||
controller: controller,
|
||||
focusNode: focusNode,
|
||||
hideOnSelect: false,
|
||||
debounceDuration: const Duration(milliseconds: 50),
|
||||
|
|
@ -447,14 +438,10 @@ class InputBar extends StatelessWidget {
|
|||
builder: (context, _, focusNode) {
|
||||
final textField = TextField(
|
||||
enableSuggestions: enableAutocorrect,
|
||||
readOnly: controller != null &&
|
||||
(controller!.choreographer.isRunningIT ||
|
||||
controller!.choreographer.chatController.obscureText),
|
||||
readOnly:
|
||||
controller != null && (controller!.choreographer.isRunningIT),
|
||||
autocorrect: enableAutocorrect,
|
||||
controller:
|
||||
(controller?.choreographer.chatController.obscureText) ?? false
|
||||
? controller?.choreographer.chatController.hideTextController
|
||||
: controller,
|
||||
controller: controller,
|
||||
focusNode: focusNode,
|
||||
contextMenuBuilder: (c, e) => markdownContextBuilder(
|
||||
c,
|
||||
|
|
|
|||
|
|
@ -74,7 +74,10 @@ class ChatInputBarState extends State<ChatInputBar> {
|
|||
),
|
||||
child: Column(
|
||||
children: [
|
||||
ReplyDisplay(widget.controller),
|
||||
// #Pangea
|
||||
if (!widget.controller.obscureText)
|
||||
// Pangea#
|
||||
ReplyDisplay(widget.controller),
|
||||
PangeaChatInputRow(
|
||||
controller: widget.controller,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -114,7 +114,9 @@ class Choreographer {
|
|||
maxWidth: 325,
|
||||
transformTargetId: inputTransformTargetKey,
|
||||
)
|
||||
: chatController.send();
|
||||
: chatController.send(
|
||||
message: chatController.sendController.text,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -135,7 +137,12 @@ class Choreographer {
|
|||
return;
|
||||
}
|
||||
|
||||
chatController.sendFakeMessage();
|
||||
if (chatController.sendController.text.trim().isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final message = chatController.sendController.text;
|
||||
final fakeEventId = chatController.sendFakeMessage();
|
||||
final PangeaRepresentation? originalWritten =
|
||||
choreoRecord.includedIT && itController.sourceText != null
|
||||
? PangeaRepresentation(
|
||||
|
|
@ -156,7 +163,7 @@ class Choreographer {
|
|||
repEventId: null,
|
||||
room: chatController.room,
|
||||
req: TokensRequestModel(
|
||||
fullText: currentText,
|
||||
fullText: message,
|
||||
senderL1: l1LangCode!,
|
||||
senderL2: l2LangCode!,
|
||||
),
|
||||
|
|
@ -167,7 +174,7 @@ class Choreographer {
|
|||
originalSent = PangeaRepresentation(
|
||||
langCode: res?.detections.firstOrNull?.langCode ??
|
||||
LanguageKeys.unknownLanguage,
|
||||
text: currentText,
|
||||
text: message,
|
||||
originalSent: true,
|
||||
originalWritten: originalWritten == null,
|
||||
);
|
||||
|
|
@ -183,7 +190,7 @@ class Choreographer {
|
|||
e: e,
|
||||
s: s,
|
||||
data: {
|
||||
"currentText": currentText,
|
||||
"currentText": message,
|
||||
"l1LangCode": l1LangCode,
|
||||
"l2LangCode": l2LangCode,
|
||||
"choreoRecord": choreoRecord.toJson(),
|
||||
|
|
@ -191,9 +198,11 @@ class Choreographer {
|
|||
);
|
||||
} finally {
|
||||
chatController.send(
|
||||
message: message,
|
||||
originalSent: originalSent,
|
||||
tokensSent: tokensSent,
|
||||
choreo: choreoRecord,
|
||||
tempEventId: fakeEventId,
|
||||
);
|
||||
clear();
|
||||
}
|
||||
|
|
@ -558,8 +567,6 @@ class Choreographer {
|
|||
choreoRecord = ChoreoRecord.newRecord;
|
||||
itController.clear();
|
||||
igc.dispose();
|
||||
//@ggurdin - why is this commented out?
|
||||
// errorService.clear();
|
||||
_resetDebounceTimer();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ class ModelKey {
|
|||
static const String messageTagMorphEdit = "morph_edit";
|
||||
static const String messageTagLemmaEdit = "lemma_edit";
|
||||
static const String messageTagActivityPlan = "activity_plan";
|
||||
static const String tempEventId = "temporary_event_id";
|
||||
|
||||
static const String baseDefinition = "base_definition";
|
||||
static const String targetDefinition = "target_definition";
|
||||
|
|
|
|||
|
|
@ -97,7 +97,10 @@ class MessageDataController extends BaseController {
|
|||
repEventId: repEventId,
|
||||
req: req,
|
||||
room: room,
|
||||
);
|
||||
).catchError((e, s) {
|
||||
_tokensCache.remove(req.hashCode);
|
||||
return Future<TokensResponseModel>.error(e, s);
|
||||
});
|
||||
|
||||
/////// translation ////////
|
||||
|
||||
|
|
|
|||
|
|
@ -202,6 +202,7 @@ extension EventsRoomExtension on Room {
|
|||
PangeaMessageTokens? tokensWritten,
|
||||
ChoreoRecord? choreo,
|
||||
String? messageTag,
|
||||
String? tempEventId,
|
||||
}) {
|
||||
// if (parseCommands) {
|
||||
// return client.parseAndRunCommand(this, message,
|
||||
|
|
@ -233,6 +234,9 @@ extension EventsRoomExtension on Room {
|
|||
if (messageTag != null) {
|
||||
event[ModelKey.messageTags] = messageTag;
|
||||
}
|
||||
if (tempEventId != null) {
|
||||
event[ModelKey.tempEventId] = tempEventId;
|
||||
}
|
||||
|
||||
if (parseMarkdown) {
|
||||
final html = markdown(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue