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
pull/1593/head
ggurdin 10 months ago committed by GitHub
parent 2357751c56
commit d8f484871e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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…
Cancel
Save