1+ import 'dart:io' ;
12import 'dart:typed_data' ;
23
34import 'package:collection/collection.dart' ;
45import 'package:file_picker/file_picker.dart' ;
56import 'package:file_selector/file_selector.dart' ;
67import 'package:matrix/matrix.dart' ;
78import 'package:mime/mime.dart' ;
9+ import 'package:path/path.dart' as p;
10+ import 'package:path_provider/path_provider.dart' ;
811import 'package:safe_change_notifier/safe_change_notifier.dart' ;
912import 'package:super_clipboard/super_clipboard.dart' ;
1013import '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