Merge branch 'krille/file-sending-status' into 'main'

feat: Display dummy event in timeline for sending files

See merge request famedly/company/frontend/famedlysdk!993
This commit is contained in:
The one with the Braid 2022-03-30 08:45:22 +00:00
commit ac24da4963
4 changed files with 109 additions and 12 deletions

View File

@ -104,6 +104,9 @@ class Client extends MatrixApi {
final Duration sendTimelineEventTimeout;
MatrixImageFileResizedResponse? Function(MatrixImageFileResizeArguments)?
customImageResizer;
/// Create a client
/// [clientName] = unique identifier of this client
/// [databaseBuilder]: A function that creates the database instance, that will be used.
@ -141,6 +144,8 @@ class Client extends MatrixApi {
/// code in background.
/// Set [timelineEventTimeout] to the preferred time the Client should retry
/// sending events on connection problems or to `Duration.zero` to disable it.
/// Set [customImageResizer] to your own implementation for a more advanced
/// and faster image resizing experience.
Client(
this.clientName, {
this.databaseBuilder,
@ -164,6 +169,7 @@ class Client extends MatrixApi {
Level? logLevel,
Filter? syncFilter,
this.sendTimelineEventTimeout = const Duration(minutes: 1),
this.customImageResizer,
@deprecated bool? debug,
}) : syncFilter = syncFilter ??
Filter(

View File

@ -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<String>(fileSendingStatusKey);
if (status == null) return null;
return FileSendingStatus.values.singleWhereOrNull(
(fileSendingStatus) => fileSendingStatus.name == status);
}
}
enum FileSendingStatus {
generatingThumbnail,
encrypting,
uploading,
}

View File

@ -50,6 +50,9 @@ const Map<HistoryVisibility, String> _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,81 @@ 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<Uri> sendFileEvent(
MatrixFile file, {
String? txid,
Event? inReplyTo,
String? editEventId,
bool waitUntilSent = false,
int? shrinkImageMaxDimension,
MatrixImageFile? thumbnail,
Map<String, dynamic>? 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,
customImageResizer: client.customImageResizer,
);
if (shrinkImageMaxDimension != null) {
file = await MatrixImageFile.shrink(
bytes: file.bytes,
name: file.name,
maxDimension: shrinkImageMaxDimension,
customImageResizer: client.customImageResizer,
);
}
}
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 +776,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 +795,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...');

View File

@ -105,14 +105,17 @@ class MatrixImageFile extends MatrixFile {
required String name,
int maxDimension = 1600,
String? mimeType,
MatrixImageFileResizedResponse? Function(MatrixImageFileResizeArguments)?
customImageResizer,
Future<T> Function<T, U>(FutureOr<T> Function(U arg) function, U arg)?
compute}) async {
final arguments = _ResizeArguments(
final arguments = MatrixImageFileResizeArguments(
bytes: bytes,
maxDimension: maxDimension,
fileName: name,
calcBlurhash: true,
);
customImageResizer ??= _resize;
final resizedData = compute != null
? await compute(_resize, arguments)
: _resize(arguments);
@ -154,6 +157,8 @@ class MatrixImageFile extends MatrixFile {
/// computes a thumbnail for the image
Future<MatrixImageFile?> generateThumbnail(
{int dimension = Client.defaultThumbnailSize,
MatrixImageFileResizedResponse? Function(MatrixImageFileResizeArguments)?
customImageResizer,
Future<T> Function<T, U>(FutureOr<T> Function(U arg) function, U arg)?
compute}) async {
final thumbnailFile = await shrink(
@ -162,6 +167,7 @@ class MatrixImageFile extends MatrixFile {
mimeType: mimeType,
compute: compute,
maxDimension: dimension,
customImageResizer: customImageResizer,
);
// the thumbnail should rather return null than the unshrinked image
if ((thumbnailFile.width ?? 0) > dimension ||
@ -171,11 +177,11 @@ class MatrixImageFile extends MatrixFile {
return thumbnailFile;
}
static _ResizedResponse? _calcMetadata(Uint8List bytes) {
static MatrixImageFileResizedResponse? _calcMetadata(Uint8List bytes) {
final image = decodeImage(bytes);
if (image == null) return null;
return _ResizedResponse(
return MatrixImageFileResizedResponse(
bytes: bytes,
width: image.width,
height: image.height,
@ -187,7 +193,8 @@ class MatrixImageFile extends MatrixFile {
);
}
static _ResizedResponse? _resize(_ResizeArguments arguments) {
static MatrixImageFileResizedResponse? _resize(
MatrixImageFileResizeArguments arguments) {
final image = decodeImage(arguments.bytes);
final resized = copyResize(image!,
@ -197,7 +204,7 @@ class MatrixImageFile extends MatrixFile {
final encoded = encodeNamedImage(resized, arguments.fileName);
if (encoded == null) return null;
final bytes = Uint8List.fromList(encoded);
return _ResizedResponse(
return MatrixImageFileResizedResponse(
bytes: bytes,
width: resized.width,
height: resized.height,
@ -212,13 +219,13 @@ class MatrixImageFile extends MatrixFile {
}
}
class _ResizedResponse {
class MatrixImageFileResizedResponse {
final Uint8List bytes;
final int width;
final int height;
final String? blurhash;
const _ResizedResponse({
const MatrixImageFileResizedResponse({
required this.bytes,
required this.width,
required this.height,
@ -226,13 +233,13 @@ class _ResizedResponse {
});
}
class _ResizeArguments {
class MatrixImageFileResizeArguments {
final Uint8List bytes;
final int maxDimension;
final String fileName;
final bool calcBlurhash;
const _ResizeArguments({
const MatrixImageFileResizeArguments({
required this.bytes,
required this.maxDimension,
required this.fileName,