Skip to content

Commit 481db8b

Browse files
authored
chore: enhance clipboard handling in input (#109)
1 parent 38b9227 commit 481db8b

File tree

5 files changed

+230
-139
lines changed

5 files changed

+230
-139
lines changed

lib/chat_room/common/view/chat_room_page.dart

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'dart:io';
33
import 'package:file_selector/file_selector.dart';
44
import 'package:flutter/material.dart';
55
import 'package:matrix/matrix.dart';
6+
import 'package:mime/mime.dart';
67
import 'package:super_drag_and_drop/super_drag_and_drop.dart';
78
import 'package:watch_it/watch_it.dart';
89

@@ -113,9 +114,13 @@ class _ChatRoomPageState extends State<ChatRoomPage> {
113114

114115
await di<DraftManager>().addAttachment(
115116
widget.room.id,
116-
onFail: (error) =>
117-
showSnackBar(context, content: Text(error)),
118-
existingFiles: [XFile.fromData(file.readAsBytesSync())],
117+
existingFiles: [
118+
XFile(
119+
value.toFilePath(),
120+
name: file.path.split(Platform.pathSeparator).last,
121+
mimeType: lookupMimeType(file.path),
122+
),
123+
],
119124
);
120125
},
121126
onError: (e) =>

lib/chat_room/create_or_edit/edit_room_service.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ class EditRoomService {
2020
Stream<JoinedRoomUpdate?> _getJoinedRoomUpdate(String? roomId) =>
2121
_joinedUpdateStream.map((e) => e.rooms?.join?[roomId]);
2222

23+
Future<void> setTyping(Room room, bool isTyping) async {
24+
try {
25+
await room.setTyping(isTyping, timeout: 500);
26+
} catch (e, s) {
27+
printMessageInDebugMode(e, s);
28+
}
29+
}
30+
2331
// ROOM NAME
2432

2533
Stream<String> getJoinedRoomNameStream(Room room) =>

lib/chat_room/input/draft_manager.dart

Lines changed: 122 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
import 'dart:io';
12
import 'dart:typed_data';
23

34
import 'package:collection/collection.dart';
45
import 'package:file_picker/file_picker.dart';
56
import 'package:file_selector/file_selector.dart';
67
import 'package:matrix/matrix.dart';
78
import 'package:mime/mime.dart';
9+
import 'package:path/path.dart' as p;
10+
import 'package:path_provider/path_provider.dart';
811
import 'package:safe_change_notifier/safe_change_notifier.dart';
912
import 'package:super_clipboard/super_clipboard.dart';
1013
import 'package:video_compress/video_compress.dart';
@@ -23,14 +26,6 @@ class DraftManager extends SafeChangeNotifier {
2326
final Client _client;
2427
final LocalImageService _localImageService;
2528

26-
bool _sending = false;
27-
bool get sending => _sending;
28-
void _setSending(bool value) {
29-
if (value == _sending) return;
30-
_sending = value;
31-
notifyListeners();
32-
}
33-
3429
Event? _replyEvent;
3530
Event? get replyEvent => _replyEvent;
3631
void setReplyEvent(Event? event) {
@@ -48,19 +43,10 @@ class DraftManager extends SafeChangeNotifier {
4843
int get maxUploadSize => _mediaConfig?.mUploadSize ?? 100 * 1000 * 1000;
4944
MediaConfig? _mediaConfig;
5045

51-
Future<void> send({
52-
required Room room,
53-
String? text,
54-
required Function(String error) onFail,
55-
required Function() onSuccess,
56-
}) async {
57-
if (_sending) return;
58-
_setSending(true);
59-
46+
Future<void> send({required Room room, String? text}) async {
6047
try {
6148
await room.setTyping(false);
6249
} on Exception catch (e, s) {
63-
onFail(e.toString());
6450
printMessageInDebugMode(e, s);
6551
}
6652

@@ -74,24 +60,35 @@ class DraftManager extends SafeChangeNotifier {
7460
String? eventId;
7561
MatrixFile? compressedFile;
7662
try {
77-
if (getCompressFile(roomId: room.id, file: matrixFile)) {
63+
if (getCompressFile(roomId: room.id, file: matrixFile) ||
64+
matrixFile.bytes.length > maxUploadSize) {
7865
_mediaConfig ??= await _client.getConfig();
7966
compressedFile = await MatrixImageFile.shrink(
8067
bytes: matrixFile.bytes,
8168
name: matrixFile.name,
8269
mimeType: matrixFile.mimeType,
83-
maxDimension: 2500,
70+
maxDimension: maxUploadSize,
8471
nativeImplementations: _client.nativeImplementations,
8572
);
8673
}
8774

75+
MatrixImageFile? thumbnail;
76+
77+
if (matrixFile is MatrixVideoFile && xFile != null) {
78+
try {
79+
thumbnail = await getVideoThumbnail(xFile);
80+
} on Exception catch (e) {
81+
printMessageInDebugMode(e);
82+
}
83+
} else {
84+
thumbnail = null;
85+
}
86+
8887
eventId = await room.sendFileEvent(
8988
compressedFile ?? matrixFile,
9089
inReplyTo: replyEvent,
9190
editEventId: _editEvents[room.id]?.eventId,
92-
thumbnail: matrixFile.mimeType.startsWith('video') && xFile != null
93-
? await getVideoThumbnail(xFile)
94-
: null,
91+
thumbnail: thumbnail,
9592
extraContent: textDraft.isNotEmpty && textDraft != 'null'
9693
? {'body': textDraft}
9794
: null,
@@ -100,9 +97,7 @@ class DraftManager extends SafeChangeNotifier {
10097
_matrixFilesToXFile.remove(matrixFile);
10198
removeCompress(roomId: room.id, file: matrixFile);
10299
}
103-
onSuccess();
104100
} on Exception catch (e, s) {
105-
onFail(e.toString());
106101
printMessageInDebugMode(e, s);
107102
}
108103
}
@@ -115,30 +110,48 @@ class DraftManager extends SafeChangeNotifier {
115110
editEventId: _editEvents[room.id]?.eventId,
116111
);
117112
} on Exception catch (e, s) {
118-
onFail(e.toString());
119113
printMessageInDebugMode(e, s);
120114
}
121115
if (eventId == null) {
122116
setTextDraft(roomId: room.id, draft: textDraft, notify: true);
123117
}
124118
}
125119

126-
_sending = false;
127120
_replyEvent = null;
128121
_editEvents[room.id] = null;
129122

130123
notifyListeners();
131124
}
132125

126+
final Map<String, int> _cursorPositions = {};
127+
int? getCursorPosition(String roomId) => _cursorPositions[roomId];
128+
void setCursorPosition({required String roomId, required int position}) {
129+
_cursorPositions[roomId] = position;
130+
// notifyListeners();
131+
}
132+
133133
final Map<String, String> _textDrafts = {};
134134
int get draftLength => _textDrafts.length;
135135
String? getTextDraft(String roomId) => _textDrafts[roomId];
136136
void setTextDraft({
137137
required String roomId,
138138
required String draft,
139139
required bool notify,
140+
bool insertAtCursor = false,
140141
}) {
141-
_textDrafts[roomId] = draft;
142+
if (insertAtCursor && _cursorPositions[roomId] != null) {
143+
final pos = _cursorPositions[roomId];
144+
final oldDraft = _textDrafts[roomId] ?? '';
145+
final newDraft =
146+
oldDraft.substring(0, pos!) +
147+
draft +
148+
oldDraft.substring(pos, oldDraft.length);
149+
_textDrafts[roomId] = newDraft;
150+
_cursorPositions[roomId] = pos + draft.length;
151+
} else {
152+
_textDrafts[roomId] = draft;
153+
}
154+
142155
if (notify) {
143156
notifyListeners();
144157
}
@@ -170,6 +183,7 @@ class DraftManager extends SafeChangeNotifier {
170183
if (files.contains(file)) {
171184
files.remove(file);
172185
_filesDrafts.update(roomId, (value) => files);
186+
_matrixFilesToXFile.remove(file);
173187
notifyListeners();
174188
}
175189
if (getFilesDraft(roomId).isEmpty) {
@@ -217,7 +231,6 @@ class DraftManager extends SafeChangeNotifier {
217231
final Map<MatrixFile, XFile> _matrixFilesToXFile = {};
218232
Future<void> addAttachment(
219233
String roomId, {
220-
required Function(String error) onFail,
221234
List<XFile>? existingFiles,
222235
}) async {
223236
setAttaching(true);
@@ -245,40 +258,47 @@ class DraftManager extends SafeChangeNotifier {
245258

246259
// TODO(Feichtmeier): add svg send support
247260
for (var xFile in xFiles!.where((e) => !e.path.contains('.svg'))) {
248-
final mime = xFile.mimeType;
249-
final bytes = await xFile.readAsBytes();
250-
MatrixFile matrixFile;
251-
if (mime?.startsWith('image') == true) {
252-
matrixFile = MatrixImageFile(
253-
bytes: bytes,
254-
name: xFile.name,
255-
mimeType: mime,
256-
);
257-
} else if (mime?.startsWith('video') == true) {
258-
matrixFile = MatrixVideoFile(
259-
bytes: bytes,
260-
name: xFile.name,
261-
mimeType: mime,
262-
);
263-
_matrixFilesToXFile.update(
264-
matrixFile,
265-
(v) => xFile,
266-
ifAbsent: () => xFile,
267-
);
268-
} else {
269-
matrixFile = MatrixFile.fromMimeType(
270-
bytes: bytes,
271-
name: xFile.name,
272-
mimeType: mime,
273-
);
274-
}
261+
MatrixFile matrixFile = await _createMatrixFileFromXFile(xFile);
275262

276263
addFileToDraft(roomId: roomId, file: matrixFile);
277264
}
278265

279266
setAttaching(false);
280267
}
281268

269+
Future<MatrixFile> _createMatrixFileFromXFile(
270+
XFile xFile, [
271+
Uint8List? data,
272+
String? name,
273+
String? mimeType,
274+
]) async {
275+
final mime = mimeType ?? xFile.mimeType;
276+
final bytes = data ?? await xFile.readAsBytes();
277+
final fileName = name ?? xFile.name;
278+
MatrixFile matrixFile;
279+
if (mime?.startsWith('image') == true) {
280+
matrixFile = MatrixImageFile(
281+
bytes: bytes,
282+
name: fileName,
283+
mimeType: mime,
284+
);
285+
} else if (mime?.startsWith('video') == true) {
286+
matrixFile = MatrixVideoFile(
287+
bytes: bytes,
288+
name: fileName,
289+
mimeType: mime,
290+
);
291+
_matrixFilesToXFile[matrixFile] = xFile;
292+
} else {
293+
matrixFile = MatrixFile.fromMimeType(
294+
bytes: bytes,
295+
name: fileName,
296+
mimeType: mime,
297+
);
298+
}
299+
return matrixFile;
300+
}
301+
282302
static const int max = 1200;
283303
static const int quality = 40;
284304

@@ -346,23 +366,34 @@ class DraftManager extends SafeChangeNotifier {
346366
if (reader.items.isEmpty) {
347367
return Future.value([]);
348368
}
369+
if (reader.canProvide(Formats.plainText) &&
370+
binaryFormats.none((format) => reader.canProvide(format))) {
371+
final text = await reader.readValue(Formats.plainText);
372+
setTextDraft(
373+
roomId: roomId,
374+
draft: text ?? '',
375+
notify: true,
376+
insertAtCursor: true,
377+
);
378+
return Future.value([]);
379+
} else {
380+
if (reader.items.none(
381+
(item) => binaryFormats.any((format) => item.canProvide(format)),
382+
)) {
383+
return Future.error(noSupportedFormatFoundInClipboard);
384+
}
349385

350-
if (reader.items.none(
351-
(item) => availableFormats.any((format) => item.canProvide(format)),
352-
)) {
353-
return Future.error(noSupportedFormatFoundInClipboard);
354-
}
355-
356-
return Future.wait(
357-
availableFormats.map(
358-
(format) => _processClipboardReader(
359-
roomId: roomId,
360-
format: format,
361-
reader: reader,
362-
fileIsTooLarge: fileIsTooLarge,
386+
return Future.wait(
387+
binaryFormats.map(
388+
(format) => _processClipboardReader(
389+
roomId: roomId,
390+
format: format,
391+
reader: reader,
392+
fileIsTooLarge: fileIsTooLarge,
393+
),
363394
),
364-
),
365-
);
395+
);
396+
}
366397
}
367398

368399
Future<void> _processClipboardReader({
@@ -380,26 +411,36 @@ class DraftManager extends SafeChangeNotifier {
380411

381412
final data = await dataReaderFile.readAll();
382413

383-
setAttaching(true);
414+
if (data.isEmpty) {
415+
return Future.value(null);
416+
}
384417

385-
addFileToDraft(
386-
roomId: roomId,
387-
file: MatrixFile.fromMimeType(
388-
bytes: Uint8List.fromList(data),
389-
name: dataReaderFile.fileName ?? 'clipboard',
390-
mimeType: format.mimeTypes?.firstOrNull,
391-
),
418+
final tempDir = await getTemporaryDirectory();
419+
final tempFile = File(p.join(tempDir.path, dataReaderFile.fileName));
420+
await tempFile.writeAsBytes(data);
421+
422+
await addAttachment(
423+
roomId,
424+
existingFiles: [
425+
XFile(
426+
tempFile.path,
427+
name: dataReaderFile.fileName ?? 'clipboard',
428+
mimeType:
429+
format.mimeTypes?.firstOrNull ??
430+
lookupMimeType(tempFile.path),
431+
),
432+
],
392433
);
393434

394-
setAttaching(false);
435+
await tempFile.delete();
395436

396437
return Future.value(null);
397438
});
398439
}
399440
}
400441
}
401442

402-
Set<SimpleFileFormat> get availableFormats => {
443+
Set<SimpleFileFormat> get binaryFormats => {
403444
Formats.jpeg,
404445
Formats.png,
405446
Formats.gif,

0 commit comments

Comments
 (0)