From e4019dadc88844f2ef3edcca35969b3361e6015b Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 9 Nov 2025 01:59:24 +0800 Subject: [PATCH] :lipstick: Optimize file upload prograss indicates --- lib/pods/chat/messages_notifier.dart | 6 +-- lib/screens/chat/room.dart | 4 +- lib/screens/posts/compose_article.dart | 2 +- lib/services/file_uploader.dart | 47 ++++++++++++++++++--- lib/widgets/chat/chat_input.dart | 2 +- lib/widgets/chat/message_item.dart | 14 +++--- lib/widgets/content/attachment_preview.dart | 5 +-- lib/widgets/post/compose_attachments.dart | 2 +- lib/widgets/post/compose_shared.dart | 4 +- lib/widgets/share/share_sheet.dart | 3 +- 10 files changed, 61 insertions(+), 28 deletions(-) diff --git a/lib/pods/chat/messages_notifier.dart b/lib/pods/chat/messages_notifier.dart index e37c858d..b725342e 100644 --- a/lib/pods/chat/messages_notifier.dart +++ b/lib/pods/chat/messages_notifier.dart @@ -28,7 +28,7 @@ class MessagesNotifier extends _$MessagesNotifier { late final SnChatMember _identity; final Map _pendingMessages = {}; - final Map> _fileUploadProgress = {}; + final Map> _fileUploadProgress = {}; int? _totalCount; String? _searchQuery; bool? _withLinks; @@ -438,7 +438,7 @@ class MessagesNotifier extends _$MessagesNotifier { SnChatMessage? editingTo, SnChatMessage? forwardingTo, SnChatMessage? replyingTo, - Function(String, Map)? onProgress, + Function(String, Map)? onProgress, }) async { final nonce = const Uuid().v4(); talker.log('Sending message with nonce $nonce'); @@ -474,7 +474,7 @@ class MessagesNotifier extends _$MessagesNotifier { fileData: attachments[idx], client: ref.read(apiClientProvider), onProgress: (progress, _) { - _fileUploadProgress[localMessage.id]?[idx] = progress; + _fileUploadProgress[localMessage.id]?[idx] = progress ?? 0.0; onProgress?.call( localMessage.id, _fileUploadProgress[localMessage.id] ?? {}, diff --git a/lib/screens/chat/room.dart b/lib/screens/chat/room.dart index 82a029e7..0a85a3b0 100644 --- a/lib/screens/chat/room.dart +++ b/lib/screens/chat/room.dart @@ -149,7 +149,7 @@ class ChatRoomScreen extends HookConsumerWidget { final messageForwardingTo = useState(null); final messageEditingTo = useState(null); final attachments = useState>([]); - final attachmentProgress = useState>>({}); + final attachmentProgress = useState>>({}); // Selection mode state final isSelectionMode = useState(false); @@ -571,7 +571,7 @@ class ChatRoomScreen extends HookConsumerWidget { onProgress: (progress, _) { attachmentProgress.value = { ...attachmentProgress.value, - 'chat-upload': {index: progress}, + 'chat-upload': {index: progress ?? 0.0}, }; }, ).future; diff --git a/lib/screens/posts/compose_article.dart b/lib/screens/posts/compose_article.dart index 5fbf6b45..09045c92 100644 --- a/lib/screens/posts/compose_article.dart +++ b/lib/screens/posts/compose_article.dart @@ -306,7 +306,7 @@ class ArticleComposeScreen extends HookConsumerWidget { ], ), children: [ - ValueListenableBuilder>( + ValueListenableBuilder>( valueListenable: state.attachmentProgress, builder: (context, progressMap, _) { return Wrap( diff --git a/lib/services/file_uploader.dart b/lib/services/file_uploader.dart index 516edd2c..33628323 100644 --- a/lib/services/file_uploader.dart +++ b/lib/services/file_uploader.dart @@ -110,6 +110,7 @@ class FileUploader { required String taskId, required int chunkIndex, required Uint8List chunkData, + ProgressCallback? onSendProgress, }) async { final formData = FormData.fromMap({ 'chunk': MultipartFile.fromBytes( @@ -121,6 +122,7 @@ class FileUploader { await _client.post( '/drive/files/upload/chunk/$taskId/$chunkIndex', data: formData, + onSendProgress: onSendProgress, ); } @@ -141,8 +143,10 @@ class FileUploader { String? encryptPassword, String? expiredAt, int? customChunkSize, + Function(double? progress, Duration estimate)? onProgress, }) async { // Step 1: Create upload task + onProgress?.call(null, Duration.zero); final createResponse = await createUploadTask( fileData: fileData, fileName: fileName, @@ -162,8 +166,17 @@ class FileUploader { final taskId = createResponse['task_id'] as String; final chunkSize = createResponse['chunk_size'] as int; final chunksCount = createResponse['chunks_count'] as int; + int totalSize; + if (fileData is XFile) { + totalSize = await fileData.length(); + } else if (fileData is Uint8List) { + totalSize = fileData.length; + } else { + throw ArgumentError('Invalid fileData type'); + } // Step 2: Upload chunks + int bytesUploaded = 0; if (fileData is XFile) { // Use stream for XFile final subscription = fileData.openRead().listen(null); @@ -171,7 +184,16 @@ class FileUploader { for (int i = 0; i < chunksCount; i++) { subscription.resume(); final chunkData = await _readNextChunk(subscription, chunkSize); - await uploadChunk(taskId: taskId, chunkIndex: i, chunkData: chunkData); + await uploadChunk( + taskId: taskId, + chunkIndex: i, + chunkData: chunkData, + onSendProgress: (sent, total) { + final overallProgress = (bytesUploaded + sent) / totalSize; + onProgress?.call(overallProgress, Duration.zero); + }, + ); + bytesUploaded += chunkData.length; } subscription.cancel(); } else if (fileData is Uint8List) { @@ -185,13 +207,23 @@ class FileUploader { // Upload each chunk for (int i = 0; i < chunks.length; i++) { - await uploadChunk(taskId: taskId, chunkIndex: i, chunkData: chunks[i]); + await uploadChunk( + taskId: taskId, + chunkIndex: i, + chunkData: chunks[i], + onSendProgress: (sent, total) { + final overallProgress = (bytesUploaded + sent) / totalSize; + onProgress?.call(overallProgress, Duration.zero); + }, + ); + bytesUploaded += chunks[i].length; } } else { throw ArgumentError('Invalid fileData type'); } // Step 3: Complete upload + onProgress?.call(null, Duration.zero); return await completeUpload(taskId); } @@ -200,7 +232,7 @@ class FileUploader { required Dio client, String? poolId, FileUploadMode? mode, - Function(double progress, Duration estimate)? onProgress, + Function(double? progress, Duration estimate)? onProgress, }) { final completer = Completer(); @@ -266,7 +298,7 @@ class FileUploader { UniversalFile fileData, Dio client, String? poolId, - Function(double progress, Duration estimate)? onProgress, + Function(double? progress, Duration estimate)? onProgress, Completer completer, ) { String actualMimetype = getMimeType(fileData); @@ -325,23 +357,24 @@ class FileUploader { required String contentType, required Dio client, String? poolId, - Function(double progress, Duration estimate)? onProgress, + Function(double? progress, Duration estimate)? onProgress, required Completer completer, }) { final uploader = FileUploader(client); // Call progress start - onProgress?.call(0.0, Duration.zero); + onProgress?.call(null, Duration.zero); uploader .uploadFile( fileData: fileData, fileName: fileName, contentType: contentType, poolId: poolId, + onProgress: onProgress, ) .then((result) { // Call progress end - onProgress?.call(1.0, Duration.zero); + onProgress?.call(null, Duration.zero); completer.complete(result); }) .catchError((e) { diff --git a/lib/widgets/chat/chat_input.dart b/lib/widgets/chat/chat_input.dart index 3e2c09eb..d7fecf45 100644 --- a/lib/widgets/chat/chat_input.dart +++ b/lib/widgets/chat/chat_input.dart @@ -44,7 +44,7 @@ class ChatInput extends HookConsumerWidget { final Function(int) onDeleteAttachment; final Function(int, int) onMoveAttachment; final Function(List) onAttachmentsChanged; - final Map> attachmentProgress; + final Map> attachmentProgress; const ChatInput({ super.key, diff --git a/lib/widgets/chat/message_item.dart b/lib/widgets/chat/message_item.dart index ae8ecc41..47556300 100644 --- a/lib/widgets/chat/message_item.dart +++ b/lib/widgets/chat/message_item.dart @@ -40,7 +40,7 @@ class MessageItem extends HookConsumerWidget { final LocalChatMessage message; final bool isCurrentUser; final Function(String action)? onAction; - final Map? progress; + final Map? progress; final bool showAvatar; final Function(String messageId) onJump; final bool isSelectionMode; @@ -689,7 +689,7 @@ class MessageHoverActionMenu extends StatelessWidget { class MessageItemDisplayBubble extends HookConsumerWidget { final LocalChatMessage message; final bool isCurrentUser; - final Map? progress; + final Map? progress; final bool showAvatar; final Function(String messageId) onJump; final String? translatedText; @@ -821,7 +821,7 @@ class MessageItemDisplayBubble extends HookConsumerWidget { class MessageItemDisplayIRC extends HookConsumerWidget { final LocalChatMessage message; final bool isCurrentUser; - final Map? progress; + final Map? progress; final bool showAvatar; final Function(String messageId) onJump; final String? translatedText; @@ -949,7 +949,7 @@ class MessageItemDisplayIRC extends HookConsumerWidget { class MessageItemDisplayDiscord extends HookConsumerWidget { final LocalChatMessage message; final bool isCurrentUser; - final Map? progress; + final Map? progress; final bool showAvatar; final Function(String messageId) onJump; final String? translatedText; @@ -1238,7 +1238,7 @@ class MessageQuoteWidget extends HookConsumerWidget { } class FileUploadProgressWidget extends StatelessWidget { - final Map? progress; + final Map? progress; final Color textColor; final bool hasContent; @@ -1266,7 +1266,9 @@ class FileUploadProgressWidget extends StatelessWidget { 'fileUploadingProgress'.tr( args: [ (entry.key + 1).toString(), - (entry.value * 100).toStringAsFixed(1), + entry.value != null + ? (entry.value! * 100).toStringAsFixed(1) + : '0.0', ], ), style: TextStyle( diff --git a/lib/widgets/content/attachment_preview.dart b/lib/widgets/content/attachment_preview.dart index 676dc544..677e040a 100644 --- a/lib/widgets/content/attachment_preview.dart +++ b/lib/widgets/content/attachment_preview.dart @@ -411,10 +411,7 @@ class AttachmentPreview extends HookConsumerWidget { ), Gap(6), Center( - child: LinearProgressIndicator( - value: - progress != null ? progress! / 100.0 : null, - ), + child: LinearProgressIndicator(value: progress), ), ], ), diff --git a/lib/widgets/post/compose_attachments.dart b/lib/widgets/post/compose_attachments.dart index c01d7d1f..024d27ae 100644 --- a/lib/widgets/post/compose_attachments.dart +++ b/lib/widgets/post/compose_attachments.dart @@ -131,7 +131,7 @@ class ArticleComposeAttachments extends ConsumerWidget { ], ), children: [ - ValueListenableBuilder>( + ValueListenableBuilder>( valueListenable: state.attachmentProgress, builder: (context, progressMap, _) { return Wrap( diff --git a/lib/widgets/post/compose_shared.dart b/lib/widgets/post/compose_shared.dart index be3509f1..719eccea 100644 --- a/lib/widgets/post/compose_shared.dart +++ b/lib/widgets/post/compose_shared.dart @@ -33,7 +33,7 @@ class ComposeState { final TextEditingController slugController; final ValueNotifier visibility; final ValueNotifier> attachments; - final ValueNotifier> attachmentProgress; + final ValueNotifier> attachmentProgress; final ValueNotifier currentPublisher; final ValueNotifier submitting; final ValueNotifier> categories; @@ -520,7 +520,7 @@ class ComposeLogic { onProgress: (progress, _) { state.attachmentProgress.value = { ...state.attachmentProgress.value, - index: progress, + index: progress ?? 0.0, }; }, ).future; diff --git a/lib/widgets/share/share_sheet.dart b/lib/widgets/share/share_sheet.dart index 831f240d..017ea203 100644 --- a/lib/widgets/share/share_sheet.dart +++ b/lib/widgets/share/share_sheet.dart @@ -246,7 +246,8 @@ class _ShareSheetState extends ConsumerState { onProgress: (progress, _) { if (mounted) { setState(() { - _fileUploadProgress[messageId]?[idx] = progress; + _fileUploadProgress[messageId]?[idx] = + progress ?? 0.0; }); } },