feat: if message needs tokenization before send, send fake message to… (#1443)
* feat: if message needs tokenization before send, send fake message to look like the message is sending * feat: make fake event replacement smoother
This commit is contained in:
parent
2357751c56
commit
d8f484871e
6 changed files with 165 additions and 2 deletions
|
|
@ -1,3 +1,5 @@
|
|||
// ignore_for_file: depend_on_referenced_packages, implementation_imports
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:core';
|
||||
import 'dart:developer';
|
||||
|
|
@ -386,6 +388,14 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
|
||||
void onInsert(int i) {
|
||||
// setState will be called by updateView() anyway
|
||||
// #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) {
|
||||
animateInEventIndex = null;
|
||||
return;
|
||||
}
|
||||
// Pangea#
|
||||
animateInEventIndex = i;
|
||||
}
|
||||
|
||||
|
|
@ -552,6 +562,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
clearSelectedEvents();
|
||||
MatrixState.pAnyState.closeOverlay();
|
||||
showToolbarStream.close();
|
||||
hideTextController.dispose();
|
||||
//Pangea#
|
||||
super.dispose();
|
||||
}
|
||||
|
|
@ -559,6 +570,10 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
// #Pangea
|
||||
// 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) {
|
||||
|
|
@ -593,6 +608,29 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
pangeaEditingEvent = null;
|
||||
}
|
||||
|
||||
String? _fakeEventID;
|
||||
bool get obscureText => _fakeEventID != null;
|
||||
|
||||
/// 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() {
|
||||
final eventID = room.sendFakeMessage(
|
||||
text: sendController.text,
|
||||
inReplyTo: replyEvent,
|
||||
editEventId: editEvent?.eventId,
|
||||
);
|
||||
setState(() => _fakeEventID = eventID);
|
||||
}
|
||||
|
||||
void clearFakeEvent() {
|
||||
if (_fakeEventID == null) return;
|
||||
timeline?.events.removeWhere((e) => e.eventId == _fakeEventID);
|
||||
setState(() {
|
||||
_fakeEventID = null;
|
||||
});
|
||||
}
|
||||
|
||||
// Future<void> send() async {
|
||||
// Original send function gets the tx id within the matrix lib,
|
||||
// but for choero, the tx id is generated before the message send.
|
||||
|
|
@ -635,6 +673,11 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
// 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.onEvent.stream.first.then((_) => clearFakeEvent());
|
||||
|
||||
room
|
||||
.pangeaSendTextEvent(
|
||||
sendController.text,
|
||||
|
|
|
|||
|
|
@ -467,7 +467,16 @@ class InputBar extends StatelessWidget {
|
|||
direction: VerticalDirection.up,
|
||||
hideOnEmpty: true,
|
||||
hideOnLoading: true,
|
||||
controller: controller,
|
||||
// #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#
|
||||
focusNode: focusNode,
|
||||
hideOnSelect: false,
|
||||
debounceDuration: const Duration(milliseconds: 50),
|
||||
|
|
@ -480,8 +489,13 @@ class InputBar extends StatelessWidget {
|
|||
readOnly:
|
||||
controller != null && controller!.choreographer.isRunningIT,
|
||||
autocorrect: false,
|
||||
// controller: controller,
|
||||
controller: (controller
|
||||
?.choreographer.chatController.obscureText) ??
|
||||
false
|
||||
? controller?.choreographer.chatController.hideTextController
|
||||
: controller,
|
||||
// Pangea#
|
||||
controller: controller,
|
||||
focusNode: focusNode,
|
||||
contextMenuBuilder: (c, e) => markdownContextBuilder(
|
||||
c,
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@ class Choreographer {
|
|||
"choreoRecord": choreoRecord.toJson(),
|
||||
},
|
||||
);
|
||||
|
||||
await igc.getIGCTextData(onlyTokensAndLanguageDetection: true);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,12 @@ class IgcController {
|
|||
try {
|
||||
if (choreographer.currentText.isEmpty) return clear();
|
||||
|
||||
// if tokenizing on message send, tokenization might take a while
|
||||
// so add a fake event to the timeline to visually indicate that the message is being sent
|
||||
if (onlyTokensAndLanguageDetection) {
|
||||
choreographer.chatController.sendFakeMessage();
|
||||
}
|
||||
|
||||
debugPrint('getIGCTextData called with ${choreographer.currentText}');
|
||||
debugPrint(
|
||||
'getIGCTextData called with tokensOnly = $onlyTokensAndLanguageDetection',
|
||||
|
|
|
|||
|
|
@ -172,6 +172,17 @@ extension PangeaRoom on Room {
|
|||
messageTag: messageTag,
|
||||
);
|
||||
|
||||
String sendFakeMessage({
|
||||
required String text,
|
||||
Event? inReplyTo,
|
||||
String? editEventId,
|
||||
}) =>
|
||||
_sendFakeMessage(
|
||||
text: text,
|
||||
inReplyTo: inReplyTo,
|
||||
editEventId: editEventId,
|
||||
);
|
||||
|
||||
// room_information
|
||||
|
||||
Future<int> get numNonAdmins async => await _numNonAdmins;
|
||||
|
|
|
|||
|
|
@ -221,6 +221,94 @@ extension EventsRoomExtension on Room {
|
|||
}
|
||||
}
|
||||
|
||||
String _sendFakeMessage({
|
||||
required String text,
|
||||
Event? inReplyTo,
|
||||
String? editEventId,
|
||||
}) {
|
||||
final content = <String, dynamic>{
|
||||
'msgtype': MessageTypes.Text,
|
||||
'body': text,
|
||||
};
|
||||
|
||||
final html = markdown(
|
||||
content['body'],
|
||||
getEmotePacks: () => getImagePacksFlat(ImagePackUsage.emoticon),
|
||||
getMention: getMention,
|
||||
);
|
||||
// if the decoded html is the same as the body, there is no need in sending a formatted message
|
||||
if (HtmlUnescape().convert(html.replaceAll(RegExp(r'<br />\n?'), '\n')) !=
|
||||
content['body']) {
|
||||
content['format'] = 'org.matrix.custom.html';
|
||||
content['formatted_body'] = html;
|
||||
}
|
||||
|
||||
// Create new transaction id
|
||||
final messageID = client.generateUniqueTransactionId();
|
||||
|
||||
if (inReplyTo != null) {
|
||||
var replyText = '<${inReplyTo.senderId}> ${inReplyTo.body}';
|
||||
replyText = replyText.split('\n').map((line) => '> $line').join('\n');
|
||||
content['format'] = 'org.matrix.custom.html';
|
||||
// be sure that we strip any previous reply fallbacks
|
||||
final replyHtml = (inReplyTo.formattedText.isNotEmpty
|
||||
? inReplyTo.formattedText
|
||||
: htmlEscape.convert(inReplyTo.body).replaceAll('\n', '<br>'))
|
||||
.replaceAll(
|
||||
RegExp(
|
||||
r'<mx-reply>.*</mx-reply>',
|
||||
caseSensitive: false,
|
||||
multiLine: false,
|
||||
dotAll: true,
|
||||
),
|
||||
'',
|
||||
);
|
||||
final repliedHtml = content.tryGet<String>('formatted_body') ??
|
||||
htmlEscape
|
||||
.convert(content.tryGet<String>('body') ?? '')
|
||||
.replaceAll('\n', '<br>');
|
||||
content['formatted_body'] =
|
||||
'<mx-reply><blockquote><a href="https://matrix.to/#/${inReplyTo.roomId!}/${inReplyTo.eventId}">In reply to</a> <a href="https://matrix.to/#/${inReplyTo.senderId}">${inReplyTo.senderId}</a><br>$replyHtml</blockquote></mx-reply>$repliedHtml';
|
||||
// We escape all @room-mentions here to prevent accidental room pings when an admin
|
||||
// replies to a message containing that!
|
||||
content['body'] =
|
||||
'${replyText.replaceAll('@room', '@\u200broom')}\n\n${content.tryGet<String>('body') ?? ''}';
|
||||
content['m.relates_to'] = {
|
||||
'm.in_reply_to': {
|
||||
'event_id': inReplyTo.eventId,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (editEventId != null) {
|
||||
final newContent = content.copy();
|
||||
content['m.new_content'] = newContent;
|
||||
content['m.relates_to'] = {
|
||||
'event_id': editEventId,
|
||||
'rel_type': RelationshipTypes.edit,
|
||||
};
|
||||
if (content['body'] is String) {
|
||||
content['body'] = '* ${content['body']}';
|
||||
}
|
||||
if (content['formatted_body'] is String) {
|
||||
content['formatted_body'] = '* ${content['formatted_body']}';
|
||||
}
|
||||
}
|
||||
|
||||
final Event event = Event(
|
||||
content: content,
|
||||
type: EventTypes.Message,
|
||||
senderId: client.userID!,
|
||||
eventId: messageID,
|
||||
room: this,
|
||||
originServerTs: DateTime.now(),
|
||||
status: EventStatus.sending,
|
||||
);
|
||||
|
||||
timeline?.events.insert(0, event);
|
||||
return messageID;
|
||||
}
|
||||
|
||||
Future<String?> _pangeaSendTextEvent(
|
||||
String message, {
|
||||
String? txid,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue