Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TW-132: sending multiples image in composer #167

Merged
merged 1 commit into from
Jun 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions lib/di/send_image/send_image_di.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import 'dart:collection';

import 'package:fluffychat/di/base_di.dart';
import 'package:fluffychat/domain/usecase/send_image_interactor.dart';
import 'package:fluffychat/domain/usecase/send_images_interactor.dart';
import 'package:get_it/get_it.dart';

class SendImageDi extends BaseDI {
Expand All @@ -8,6 +11,9 @@ class SendImageDi extends BaseDI {

@override
void setUp(GetIt get) {
get.registerSingleton<Queue>(Queue());

get.registerSingleton<SendImageInteractor>(SendImageInteractor());
get.registerSingleton<SendImagesInteractor>(SendImagesInteractor());
}
}
29 changes: 29 additions & 0 deletions lib/domain/usecase/send_images_interactor.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import 'package:fluffychat/presentation/extensions/room_extension.dart';
import 'package:matrix/matrix.dart';
import 'package:photo_manager/photo_manager.dart';

class SendImagesInteractor {

Future<void> execute({
required Room room,
required List<AssetEntity> entities,
}) async {
try {
final txIdMapToImageInfo = await room.sendPlaceholdersForImages(
entities: entities,
);

for (final txId in txIdMapToImageInfo.value1.keys) {
await room.sendImageFileEvent(
txIdMapToImageInfo.value1[txId]!,
fakeImageEvent: txIdMapToImageInfo.value2[txId],
txid: txId,
);

room.clearOlderImagesCacheInRoom();
}
} catch (error) {
Logs().d("SendImageInteractor: execute(): $error");
}
}
}
21 changes: 13 additions & 8 deletions lib/pages/chat/chat.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:io';

import 'package:fluffychat/di/global/get_it_initializer.dart';
import 'package:fluffychat/domain/usecase/send_image_interactor.dart';
import 'package:fluffychat/domain/usecase/send_images_interactor.dart';
import 'package:fluffychat/pages/forward/forward.dart';
import 'package:fluffychat/utils/network_connection_service.dart';
import 'package:fluffychat/utils/voip/permission_service.dart';
Expand Down Expand Up @@ -408,15 +409,19 @@ class ChatController extends State<Chat> {
}
}

// Future<void> sendImages() async {
// final selectedAssets = imagePickerController.sortedSelectedAssets;
// for (final entity in selectedAssets) {
// await sendImage();
// }
Future<void> sendImages() async {
final selectedAssets = imagePickerController.sortedSelectedAssets;
final sendImagesInteractor = getIt.get<SendImagesInteractor>();
sendImagesInteractor.execute(
room: room!,
entities: selectedAssets
.map<AssetEntity>((entity) => entity.asset)
.toList()
);

// imagePickerController.clearAssetCounter();
// numberSelectedImagesNotifier.value = 0;
// }
imagePickerController.clearAssetCounter();
numberSelectedImagesNotifier.value = 0;
}

void openCameraAction() async {
// Make sure the textfield is unfocused before opening the camera
Expand Down
2 changes: 1 addition & 1 deletion lib/pages/chat/chat_input_row.dart
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ Future<void> showImagesPickerBottomSheet({
InkWell(
borderRadius: const BorderRadius.all(Radius.circular(100)),
onTap: () {
controller.sendImage();
controller.sendImages();
Navigator.of(context).pop();
},
child: SvgPicture.asset(
Expand Down
171 changes: 113 additions & 58 deletions lib/presentation/extensions/room_extension.dart
Original file line number Diff line number Diff line change
@@ -1,65 +1,38 @@
import 'dart:collection';

import 'package:dartz/dartz.dart' hide id;
import 'package:fluffychat/di/global/get_it_initializer.dart';
import 'package:fluffychat/presentation/extensions/asset_entity_extension.dart';
import 'package:flutter/widgets.dart';
import 'package:matrix/matrix.dart';
import 'package:matrix/src/utils/file_send_request_credentials.dart';
import 'package:photo_manager/photo_manager.dart';

typedef TransactionId = String;

typedef FakeImageEvent = SyncUpdate;
extension SendImage on Room {

static const maxSendingImage = 20;
static const maxImagesCacheInRoom = 10;

Future<String?> sendImageFileEvent(
MatrixFile file, {
SyncUpdate? fakeImageEvent,
String? txid,
Event? inReplyTo,
String? editEventId,
int? shrinkImageMaxDimension,
Map<String, dynamic>? extraContent,
}) async {
if (sendingFileThumbnails.entries.length > maxSendingImage) {
sendingFileThumbnails.clear();
}
if (sendingFilePlaceholders.entries.length > maxSendingImage) {
sendingFilePlaceholders.clear();
}

txid ??= client.generateUniqueTransactionId();
sendingFilePlaceholders[txid] = file;
sendingFileThumbnails[txid] = MatrixImageFile(bytes: file.bytes, name: file.name);

// 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,
...FileSendRequestCredentials(
inReplyTo: inReplyTo?.eventId,
editEventId: editEventId,
shrinkImageMaxDimension: shrinkImageMaxDimension,
extraContent: extraContent,
).toJson(),
},
),
],
),
),
},
),
fakeImageEvent ??= await sendFakeImageEvent(
file,
txid: txid,
inReplyTo: inReplyTo,
editEventId: editEventId,
shrinkImageMaxDimension: shrinkImageMaxDimension,
extraContent: extraContent,
);
await handleImageFakeSync(syncUpdate);
// Check media config of the server before sending the file. Stop if the
// Media config is unreachable or the file is bigger than the given maxsize.
try {
Expand All @@ -70,9 +43,9 @@ extension SendImage on Room {
}
} catch (e) {
Logs().d('Config error while sending file', e);
syncUpdate.rooms!.join!.values.first.timeline!.events!.first
fakeImageEvent.rooms!.join!.values.first.timeline!.events!.first
.unsigned![messageSendingStatusKey] = EventStatus.error.intValue;
await handleImageFakeSync(syncUpdate);
await handleImageFakeSync(fakeImageEvent);
rethrow;
}

Expand All @@ -97,9 +70,9 @@ extension SendImage on Room {
EncryptedFile? encryptedFile;
EncryptedFile? encryptedThumbnail;
if (encrypted && client.fileEncryptionEnabled) {
syncUpdate.rooms!.join!.values.first.timeline!.events!.first
fakeImageEvent.rooms!.join!.values.first.timeline!.events!.first
.unsigned![fileSendingStatusKey] = FileSendingStatus.encrypting.name;
await handleImageFakeSync(syncUpdate);
await handleImageFakeSync(fakeImageEvent);
encryptedFile = await file.encrypt();
uploadFile = encryptedFile.toMatrixFile();

Expand All @@ -112,7 +85,7 @@ extension SendImage on Room {

final timeoutDate = DateTime.now();

syncUpdate.rooms!.join!.values.first.timeline!.events!.first
fakeImageEvent.rooms!.join!.values.first.timeline!.events!.first
.unsigned![fileSendingStatusKey] = FileSendingStatus.uploading.name;
while (uploadResp == null ||
(uploadThumbnail != null && thumbnailUploadResp == null)) {
Expand All @@ -130,15 +103,15 @@ extension SendImage on Room {
)
: null;
} on MatrixException catch (_) {
syncUpdate.rooms!.join!.values.first.timeline!.events!.first
fakeImageEvent.rooms!.join!.values.first.timeline!.events!.first
.unsigned![messageSendingStatusKey] = EventStatus.error.intValue;
await handleImageFakeSync(syncUpdate);
await handleImageFakeSync(fakeImageEvent);
rethrow;
} catch (_) {
if (DateTime.now().isAfter(timeoutDate)) {
syncUpdate.rooms!.join!.values.first.timeline!.events!.first
fakeImageEvent.rooms!.join!.values.first.timeline!.events!.first
.unsigned![messageSendingStatusKey] = EventStatus.error.intValue;
await handleImageFakeSync(syncUpdate);
await handleImageFakeSync(fakeImageEvent);
rethrow;
}
Logs().v('Send File into room failed. Try again...');
Expand Down Expand Up @@ -203,14 +176,96 @@ extension SendImage on Room {
return eventId;
}

Future<void> handleImageFakeSync(SyncUpdate syncUpdate,
Future<SyncUpdate> sendFakeImageEvent(
MatrixFile file, {
required String txid,
Event? inReplyTo,
String? editEventId,
int? shrinkImageMaxDimension,
Map<String, dynamic>? extraContent,
}) async {
sendingFilePlaceholders[txid] = file;
sendingFileThumbnails[txid] = MatrixImageFile(bytes: file.bytes, name: file.name);

// Create a fake Event object as a placeholder for the uploading file:
final fakeImageEventEvent = 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,
...FileSendRequestCredentials(
inReplyTo: inReplyTo?.eventId,
editEventId: editEventId,
shrinkImageMaxDimension: shrinkImageMaxDimension,
extraContent: extraContent,
).toJson(),
},
),
],
),
),
},
),
);
await handleImageFakeSync(fakeImageEventEvent);
return fakeImageEventEvent;
}

Future<void> handleImageFakeSync(SyncUpdate fakeImageEvent,
{Direction? direction}) async {
if (client.database != null) {
await client.database?.transaction(() async {
await client.handleSync(syncUpdate, direction: direction);
await client.handleSync(fakeImageEvent, direction: direction);
});
} else {
await client.handleSync(syncUpdate, direction: direction);
await client.handleSync(fakeImageEvent, direction: direction);
}
}

void clearOlderImagesCacheInRoom() {
final imageCacheQueue = getIt.get<Queue>();
// clear older image cache
while (imageCacheQueue.length >= maxImagesCacheInRoom) {
final txId = imageCacheQueue.removeFirst();
if (sendingFilePlaceholders.containsKey(txId)) {
sendingFilePlaceholders.remove(txId);
}
if (sendingFileThumbnails.containsKey(txId)) {
sendingFileThumbnails.remove(txId);
}
}
}

Future<Tuple2<Map<TransactionId, MatrixFile>, Map<TransactionId, FakeImageEvent>>> sendPlaceholdersForImages({
required List<AssetEntity> entities,
}) async {
final imageCacheQueue = getIt.get<Queue>();
final txIdMapToImageFile = Tuple2<Map<TransactionId, MatrixFile>, Map<TransactionId, FakeImageEvent>>({}, {});
for (final entity in entities) {
final matrixFile = await entity.toMatrixFile();
if (matrixFile != null) {
final txid = client.generateUniqueTransactionId();
final fakeImageEvent = await sendFakeImageEvent(matrixFile, txid: txid);
txIdMapToImageFile.value1[txid] = matrixFile;
txIdMapToImageFile.value2[txid] = fakeImageEvent;
imageCacheQueue.add(txid);
}
}
return txIdMapToImageFile;
}
}
6 changes: 3 additions & 3 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2264,13 +2264,13 @@ packages:
source: hosted
version: "0.0.5"
tuple:
dependency: transitive
dependency: "direct main"
description:
name: tuple
sha256: "0ea99cd2f9352b2586583ab2ce6489d1f95a5f6de6fb9492faaf97ae2060f0aa"
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "2.0.2"
typed_data:
dependency: transitive
description:
Expand Down
3 changes: 2 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ publish_to: none
version: 2.1.2+2330

environment:
sdk: ">=2.17.0 <3.0.0"
sdk: ">=2.19.0 <3.0.0"

dependencies:
adaptive_dialog: ^1.8.0+1
Expand Down Expand Up @@ -116,6 +116,7 @@ dependencies:
rxdart: ^0.27.7
photo_manager: ^2.6.0
flutter_inappwebview: ^5.7.2+3
tuple: ^2.0.2
dev_dependencies:
build_runner: ^2.3.3
dart_code_metrics: ^5.7.3
Expand Down