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:
commit
ac24da4963
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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...');
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue