From 11fb79623ed322faf6a33b02171ca20090b44a73 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Fri, 2 Aug 2024 15:49:32 +0800 Subject: [PATCH] :sparkles: Attachment can link exists things :wheelchair: Optimize upload progress --- lib/providers/attachment_uploader.dart | 3 +- lib/providers/content/attachment.dart | 31 +++---- lib/screens/account/personalize.dart | 7 +- lib/translations/en_us.dart | 4 + lib/translations/zh_cn.dart | 4 + .../attachments/attachment_editor.dart | 80 ++++++++++++++++++- lib/widgets/chat/chat_event_list.dart | 6 +- 7 files changed, 111 insertions(+), 24 deletions(-) diff --git a/lib/providers/attachment_uploader.dart b/lib/providers/attachment_uploader.dart index 3cedd94..ed50ddb 100644 --- a/lib/providers/attachment_uploader.dart +++ b/lib/providers/attachment_uploader.dart @@ -180,14 +180,13 @@ class AttachmentUploaderController extends GetxController { {Function(double)? onProgress}) async { final AttachmentProvider provider = Get.find(); try { - final resp = await provider.createAttachment( + final result = await provider.createAttachment( data, path, usage, metadata, onProgress: onProgress, ); - var result = Attachment.fromJson(resp.body); return result; } catch (err) { rethrow; diff --git a/lib/providers/content/attachment.dart b/lib/providers/content/attachment.dart index 5c9af93..847e1d0 100644 --- a/lib/providers/content/attachment.dart +++ b/lib/providers/content/attachment.dart @@ -6,6 +6,7 @@ import 'package:path/path.dart'; import 'package:solian/models/attachment.dart'; import 'package:solian/providers/auth.dart'; import 'package:solian/services.dart'; +import 'package:dio/dio.dart' as dio; class AttachmentProvider extends GetConnect { static Map mimetypeOverrides = { @@ -37,18 +38,14 @@ class AttachmentProvider extends GetConnect { return null; } - Future createAttachment( + Future createAttachment( Uint8List data, String path, String usage, Map? metadata, {Function(double)? onProgress}) async { final AuthProvider auth = Get.find(); if (auth.isAuthorized.isFalse) throw Exception('unauthorized'); - final client = auth.configureClient( - 'files', - timeout: const Duration(minutes: 3), - ); - - final filePayload = MultipartFile(data, filename: basename(path)); + final filePayload = + dio.MultipartFile.fromBytes(data, filename: basename(path)); final fileAlt = basename(path).contains('.') ? basename(path).substring(0, basename(path).lastIndexOf('.')) : basename(path); @@ -61,25 +58,31 @@ class AttachmentProvider extends GetConnect { if (mimetypeOverrides.keys.contains(fileExt)) { mimetypeOverride = mimetypeOverrides[fileExt]; } - final payload = FormData({ + final payload = dio.FormData.fromMap({ 'alt': fileAlt, 'file': filePayload, 'usage': usage, if (mimetypeOverride != null) 'mimetype': mimetypeOverride, 'metadata': jsonEncode(metadata), }); - final resp = await client.post( + final resp = await dio.Dio( + dio.BaseOptions( + baseUrl: ServiceFinder.buildUrl('files', null), + headers: {'Authorization': 'Bearer ${auth.credentials!.accessToken}'}, + ), + ).post( '/attachments', - payload, - uploadProgress: (progress) { - if (onProgress != null) onProgress(progress); + data: payload, + onSendProgress: (count, total) { + if (onProgress != null) onProgress(count / total); }, ); if (resp.statusCode != 200) { - throw Exception(resp.bodyString); + print(resp.data); + throw Exception(resp.data); } - return resp; + return Attachment.fromJson(resp.data); } Future updateAttachment( diff --git a/lib/screens/account/personalize.dart b/lib/screens/account/personalize.dart index 25c48ba..1f0cf53 100644 --- a/lib/screens/account/personalize.dart +++ b/lib/screens/account/personalize.dart @@ -7,6 +7,7 @@ import 'package:image_cropper/image_cropper.dart'; import 'package:image_picker/image_picker.dart'; import 'package:intl/intl.dart'; import 'package:solian/exts.dart'; +import 'package:solian/models/attachment.dart'; import 'package:solian/providers/auth.dart'; import 'package:solian/providers/content/attachment.dart'; import 'package:solian/services.dart'; @@ -104,9 +105,9 @@ class _PersonalizeScreenState extends State { final AttachmentProvider provider = Get.find(); - Response? attachResp; + Attachment? attachResult; try { - attachResp = await provider.createAttachment( + attachResult = await provider.createAttachment( await file.readAsBytes(), file.path, 'p.$position', @@ -122,7 +123,7 @@ class _PersonalizeScreenState extends State { final resp = await client.put( '/users/me/$position', - {'attachment': attachResp.body['id']}, + {'attachment': attachResult.id}, ); if (resp.statusCode == 200) { _syncWidget(); diff --git a/lib/translations/en_us.dart b/lib/translations/en_us.dart index 1f3da71..51b47f4 100644 --- a/lib/translations/en_us.dart +++ b/lib/translations/en_us.dart @@ -13,6 +13,7 @@ const i18nEnglish = { 'more': 'More', 'share': 'Share', 'feed': 'Feed', + 'unlink': 'Unlink', 'feedSearch': 'Search Feed', 'feedSearchWithTag': 'Searching with tag #@key', 'feedSearchWithCategory': 'Searching in category @category', @@ -171,6 +172,9 @@ const i18nEnglish = { 'attachmentAddCameraVideo': 'Capture video', 'attachmentAddClipboard': 'Paste file', 'attachmentAddFile': 'Attach file', + 'attachmentAddLink': 'Link attachments', + 'attachmentAddLinkHint': 'Enter attachment serial number to link that attachment', + 'attachmentAddLinkInput': 'Serial number', 'attachmentSetting': 'Adjust attachment', 'attachmentAlt': 'Alternative text', 'attachmentLoadFailed': 'Load Attachment Failed', diff --git a/lib/translations/zh_cn.dart b/lib/translations/zh_cn.dart index 17feab7..e3fc5fa 100644 --- a/lib/translations/zh_cn.dart +++ b/lib/translations/zh_cn.dart @@ -21,6 +21,7 @@ const i18nSimplifiedChinese = { 'more': '更多', 'share': '分享', 'feed': '资讯', + 'unlink': '移除链接', 'feedSearch': '搜索资讯', 'feedSearchWithTag': '检索带有 #@key 标签的资讯', 'feedSearchWithCategory': '检索位于分类 @category 的资讯', @@ -160,6 +161,9 @@ const i18nSimplifiedChinese = { 'attachmentAddCameraVideo': '拍摄视频', 'attachmentAddClipboard': '粘贴文件', 'attachmentAddFile': '附加文件', + 'attachmentAddLink': '链接附件', + 'attachmentAddLinkHint': '输入附件的神秘代号来链接对应附件', + 'attachmentAddLinkInput': '神秘代号', 'attachmentSetting': '调整附件', 'attachmentAlt': '替代文字', 'attachmentLoadFailed': '加载失败', diff --git a/lib/widgets/attachments/attachment_editor.dart b/lib/widgets/attachments/attachment_editor.dart index c061046..4f5dca5 100644 --- a/lib/widgets/attachments/attachment_editor.dart +++ b/lib/widgets/attachments/attachment_editor.dart @@ -110,6 +110,63 @@ class _AttachmentEditorPopupState extends State { ); } + Future _linkAttachments() async { + final controller = TextEditingController(); + final input = await showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text('attachmentAddLink'.tr), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text('attachmentAddLinkHint'.tr, textAlign: TextAlign.left), + const SizedBox(height: 18), + TextField( + controller: controller, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: 'attachmentAddLinkInput'.tr, + ), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), + ), + ], + ), + actions: [ + TextButton( + style: TextButton.styleFrom( + foregroundColor: + Theme.of(context).colorScheme.onSurface.withOpacity(0.8), + ), + onPressed: () => Navigator.pop(context), + child: Text('cancel'.tr), + ), + TextButton( + child: Text('next'.tr), + onPressed: () { + Navigator.pop(context, controller.text); + }, + ), + ], + ); + }, + ); + + WidgetsBinding.instance.addPostFrameCallback((_) => controller.dispose()); + + if (input == null || input.isEmpty) return; + final value = int.tryParse(input); + if (value == null) return; + + final AttachmentProvider attach = Get.find(); + final result = await attach.getMetadata(value); + if (result != null) { + widget.onAdd(result.id); + setState(() => _attachments.add(result)); + } + } + void _pasteFileToUpload() async { final data = await Pasteboard.image; if (data == null) return; @@ -150,7 +207,7 @@ class _AttachmentEditorPopupState extends State { } void _revertMetadataList() { - final AttachmentProvider provider = Get.find(); + final AttachmentProvider attach = Get.find(); if (widget.initialAttachments.isEmpty) { _isFirstTimeBusy = false; @@ -167,7 +224,7 @@ class _AttachmentEditorPopupState extends State { int progress = 0; for (var idx = 0; idx < widget.initialAttachments.length; idx++) { - provider.getMetadata(widget.initialAttachments[idx]).then((resp) { + attach.getMetadata(widget.initialAttachments[idx]).then((resp) { progress++; _attachments[idx] = resp; if (progress == widget.initialAttachments.length) { @@ -425,6 +482,19 @@ class _AttachmentEditorPopupState extends State { }); }, ), + PopupMenuItem( + child: ListTile( + title: Text('unlink'.tr), + leading: const Icon(Icons.link_off), + contentPadding: const EdgeInsets.symmetric( + horizontal: 8, + ), + ), + onTap: () { + widget.onRemove(element.id); + setState(() => _attachments.removeAt(index)); + }, + ), ], ), ], @@ -661,6 +731,12 @@ class _AttachmentEditorPopupState extends State { style: const ButtonStyle(visualDensity: density), onPressed: () => _pickFileToUpload(), ), + ElevatedButton.icon( + icon: const Icon(Icons.link), + label: Text('attachmentAddFile'.tr), + style: const ButtonStyle(visualDensity: density), + onPressed: () => _linkAttachments(), + ), ], ).paddingSymmetric(horizontal: 12), ), diff --git a/lib/widgets/chat/chat_event_list.dart b/lib/widgets/chat/chat_event_list.dart index 18c43b8..13342b4 100644 --- a/lib/widgets/chat/chat_event_list.dart +++ b/lib/widgets/chat/chat_event_list.dart @@ -24,7 +24,7 @@ class ChatEventList extends StatelessWidget { required this.onReply, }); - bool checkMessageMergeable(Event? a, Event? b) { + bool _checkMessageMergeable(Event? a, Event? b) { if (a == null || b == null) return false; if (a.sender.account.id != b.sender.account.id) return false; return a.createdAt.difference(b.createdAt).inMinutes <= 3; @@ -42,13 +42,13 @@ class ChatEventList extends StatelessWidget { itemBuilder: (context, index) { bool isMerged = false, hasMerged = false; if (index > 0) { - hasMerged = checkMessageMergeable( + hasMerged = _checkMessageMergeable( chatController.currentEvents[index - 1].data, chatController.currentEvents[index].data, ); } if (index + 1 < chatController.currentEvents.length) { - isMerged = checkMessageMergeable( + isMerged = _checkMessageMergeable( chatController.currentEvents[index].data, chatController.currentEvents[index + 1].data, );