From a915cdacc8b67c25c32a60480e7d2e7f681eeba3 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 30 Mar 2022 09:17:21 +0200 Subject: [PATCH] feat: Display dummy event in timeline for sending files For thumbnail generation, encrypting and uploading it is not necessary to block the UI. The given file event should already be displayed in the timeline. This placed it in the UI and adds a additional fileSendingStatus property so the app can fetch the current status. --- lib/src/event.dart | 16 +++++++++++ lib/src/room.dart | 71 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/lib/src/event.dart b/lib/src/event.dart index 11c71319..305b1dd0 100644 --- a/lib/src/event.dart +++ b/lib/src/event.dart @@ -19,6 +19,7 @@ import 'dart:convert'; import 'dart:typed_data'; +import 'package:collection/collection.dart'; import 'package:html/parser.dart'; import 'package:http/http.dart' as http; @@ -796,4 +797,19 @@ class Event extends MatrixEvent { return _countEmojiRegex.allMatches(plaintextBody).length; } } + + /// If this event is in Status SENDING and it aims to send a file, then this + /// shows the status of the file sending. + FileSendingStatus? get fileSendingStatus { + final status = unsigned?.tryGet(fileSendingStatusKey); + if (status == null) return null; + return FileSendingStatus.values.singleWhereOrNull( + (fileSendingStatus) => fileSendingStatus.name == status); + } +} + +enum FileSendingStatus { + generatingThumbnail, + encrypting, + uploading, } diff --git a/lib/src/room.dart b/lib/src/room.dart index 3461b5cf..6f22754a 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -50,6 +50,9 @@ const Map _historyVisibilityMap = { const String messageSendingStatusKey = 'com.famedly.famedlysdk.message_sending_status'; +const String fileSendingStatusKey = + 'com.famedly.famedlysdk.file_sending_status'; + const String sortOrderKey = 'com.famedly.famedlysdk.sort_order'; /// Represents a Matrix room. @@ -687,25 +690,78 @@ class Room { /// /// In case [file] is a [MatrixImageFile], [thumbnail] is automatically /// computed unless it is explicitly provided. + /// Set [shrinkImageMaxDimension] to for example `1600` if you want to shrink + /// your image before sending. This is ignored if the File is not a + /// [MatrixImageFile]. Future sendFileEvent( MatrixFile file, { String? txid, Event? inReplyTo, String? editEventId, bool waitUntilSent = false, + int? shrinkImageMaxDimension, MatrixImageFile? thumbnail, Map? extraContent, }) async { + txid ??= client.generateUniqueTransactionId(); + + // Create a fake Event object as a placeholder for the uploading file: + final syncUpdate = SyncUpdate( + nextBatch: '', + rooms: RoomsUpdate( + join: { + id: JoinedRoomUpdate( + timeline: TimelineUpdate( + events: [ + MatrixEvent( + content: { + 'msgtype': file.msgType, + 'body': file.name, + 'filename': file.name, + }, + type: EventTypes.Message, + eventId: txid, + senderId: client.userID!, + originServerTs: DateTime.now(), + unsigned: { + messageSendingStatusKey: EventStatus.sending.intValue, + 'transaction_id': txid, + }, + ), + ], + ), + ), + }, + ), + ); + MatrixFile uploadFile = file; // ignore: omit_local_variable_types // computing the thumbnail in case we can - thumbnail ??= (file is MatrixImageFile && encrypted - ? await file.generateThumbnail(compute: client.runInBackground) - : null); + if (file is MatrixImageFile && + (thumbnail == null || shrinkImageMaxDimension != null)) { + syncUpdate.rooms!.join!.values.first.timeline!.events!.first + .unsigned![fileSendingStatusKey] = + FileSendingStatus.generatingThumbnail.name; + await _handleFakeSync(syncUpdate); + thumbnail ??= + await file.generateThumbnail(compute: client.runInBackground); + if (shrinkImageMaxDimension != null) { + file = await MatrixImageFile.shrink( + bytes: file.bytes, + name: file.name, + maxDimension: shrinkImageMaxDimension, + ); + } + } + MatrixFile? uploadThumbnail = thumbnail; // ignore: omit_local_variable_types EncryptedFile? encryptedFile; EncryptedFile? encryptedThumbnail; if (encrypted && client.fileEncryptionEnabled) { + syncUpdate.rooms!.join!.values.first.timeline!.events!.first + .unsigned![fileSendingStatusKey] = FileSendingStatus.encrypting.name; + await _handleFakeSync(syncUpdate); encryptedFile = await file.encrypt(); uploadFile = encryptedFile.toMatrixFile(); @@ -717,6 +773,9 @@ class Room { Uri? uploadResp, thumbnailUploadResp; final timeoutDate = DateTime.now().add(client.sendTimelineEventTimeout); + + syncUpdate.rooms!.join!.values.first.timeline!.events!.first + .unsigned![fileSendingStatusKey] = FileSendingStatus.uploading.name; while (uploadResp == null || (uploadThumbnail != null && thumbnailUploadResp == null)) { try { @@ -733,9 +792,15 @@ class Room { ) : null; } on MatrixException catch (_) { + syncUpdate.rooms!.join!.values.first.timeline!.events!.first + .unsigned![messageSendingStatusKey] = EventStatus.error.intValue; + await _handleFakeSync(syncUpdate); rethrow; } catch (_) { if (DateTime.now().isAfter(timeoutDate)) { + syncUpdate.rooms!.join!.values.first.timeline!.events!.first + .unsigned![messageSendingStatusKey] = EventStatus.error.intValue; + await _handleFakeSync(syncUpdate); rethrow; } Logs().v('Send File into room failed. Try again...');