💄 Optimize file upload prograss indicates
This commit is contained in:
@@ -28,7 +28,7 @@ class MessagesNotifier extends _$MessagesNotifier {
|
|||||||
late final SnChatMember _identity;
|
late final SnChatMember _identity;
|
||||||
|
|
||||||
final Map<String, LocalChatMessage> _pendingMessages = {};
|
final Map<String, LocalChatMessage> _pendingMessages = {};
|
||||||
final Map<String, Map<int, double>> _fileUploadProgress = {};
|
final Map<String, Map<int, double?>> _fileUploadProgress = {};
|
||||||
int? _totalCount;
|
int? _totalCount;
|
||||||
String? _searchQuery;
|
String? _searchQuery;
|
||||||
bool? _withLinks;
|
bool? _withLinks;
|
||||||
@@ -438,7 +438,7 @@ class MessagesNotifier extends _$MessagesNotifier {
|
|||||||
SnChatMessage? editingTo,
|
SnChatMessage? editingTo,
|
||||||
SnChatMessage? forwardingTo,
|
SnChatMessage? forwardingTo,
|
||||||
SnChatMessage? replyingTo,
|
SnChatMessage? replyingTo,
|
||||||
Function(String, Map<int, double>)? onProgress,
|
Function(String, Map<int, double?>)? onProgress,
|
||||||
}) async {
|
}) async {
|
||||||
final nonce = const Uuid().v4();
|
final nonce = const Uuid().v4();
|
||||||
talker.log('Sending message with nonce $nonce');
|
talker.log('Sending message with nonce $nonce');
|
||||||
@@ -474,7 +474,7 @@ class MessagesNotifier extends _$MessagesNotifier {
|
|||||||
fileData: attachments[idx],
|
fileData: attachments[idx],
|
||||||
client: ref.read(apiClientProvider),
|
client: ref.read(apiClientProvider),
|
||||||
onProgress: (progress, _) {
|
onProgress: (progress, _) {
|
||||||
_fileUploadProgress[localMessage.id]?[idx] = progress;
|
_fileUploadProgress[localMessage.id]?[idx] = progress ?? 0.0;
|
||||||
onProgress?.call(
|
onProgress?.call(
|
||||||
localMessage.id,
|
localMessage.id,
|
||||||
_fileUploadProgress[localMessage.id] ?? {},
|
_fileUploadProgress[localMessage.id] ?? {},
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
final messageForwardingTo = useState<SnChatMessage?>(null);
|
final messageForwardingTo = useState<SnChatMessage?>(null);
|
||||||
final messageEditingTo = useState<SnChatMessage?>(null);
|
final messageEditingTo = useState<SnChatMessage?>(null);
|
||||||
final attachments = useState<List<UniversalFile>>([]);
|
final attachments = useState<List<UniversalFile>>([]);
|
||||||
final attachmentProgress = useState<Map<String, Map<int, double>>>({});
|
final attachmentProgress = useState<Map<String, Map<int, double?>>>({});
|
||||||
|
|
||||||
// Selection mode state
|
// Selection mode state
|
||||||
final isSelectionMode = useState<bool>(false);
|
final isSelectionMode = useState<bool>(false);
|
||||||
@@ -571,7 +571,7 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
onProgress: (progress, _) {
|
onProgress: (progress, _) {
|
||||||
attachmentProgress.value = {
|
attachmentProgress.value = {
|
||||||
...attachmentProgress.value,
|
...attachmentProgress.value,
|
||||||
'chat-upload': {index: progress},
|
'chat-upload': {index: progress ?? 0.0},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
).future;
|
).future;
|
||||||
|
|||||||
@@ -306,7 +306,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
ValueListenableBuilder<Map<int, double>>(
|
ValueListenableBuilder<Map<int, double?>>(
|
||||||
valueListenable: state.attachmentProgress,
|
valueListenable: state.attachmentProgress,
|
||||||
builder: (context, progressMap, _) {
|
builder: (context, progressMap, _) {
|
||||||
return Wrap(
|
return Wrap(
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ class FileUploader {
|
|||||||
required String taskId,
|
required String taskId,
|
||||||
required int chunkIndex,
|
required int chunkIndex,
|
||||||
required Uint8List chunkData,
|
required Uint8List chunkData,
|
||||||
|
ProgressCallback? onSendProgress,
|
||||||
}) async {
|
}) async {
|
||||||
final formData = FormData.fromMap({
|
final formData = FormData.fromMap({
|
||||||
'chunk': MultipartFile.fromBytes(
|
'chunk': MultipartFile.fromBytes(
|
||||||
@@ -121,6 +122,7 @@ class FileUploader {
|
|||||||
await _client.post(
|
await _client.post(
|
||||||
'/drive/files/upload/chunk/$taskId/$chunkIndex',
|
'/drive/files/upload/chunk/$taskId/$chunkIndex',
|
||||||
data: formData,
|
data: formData,
|
||||||
|
onSendProgress: onSendProgress,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,8 +143,10 @@ class FileUploader {
|
|||||||
String? encryptPassword,
|
String? encryptPassword,
|
||||||
String? expiredAt,
|
String? expiredAt,
|
||||||
int? customChunkSize,
|
int? customChunkSize,
|
||||||
|
Function(double? progress, Duration estimate)? onProgress,
|
||||||
}) async {
|
}) async {
|
||||||
// Step 1: Create upload task
|
// Step 1: Create upload task
|
||||||
|
onProgress?.call(null, Duration.zero);
|
||||||
final createResponse = await createUploadTask(
|
final createResponse = await createUploadTask(
|
||||||
fileData: fileData,
|
fileData: fileData,
|
||||||
fileName: fileName,
|
fileName: fileName,
|
||||||
@@ -162,8 +166,17 @@ class FileUploader {
|
|||||||
final taskId = createResponse['task_id'] as String;
|
final taskId = createResponse['task_id'] as String;
|
||||||
final chunkSize = createResponse['chunk_size'] as int;
|
final chunkSize = createResponse['chunk_size'] as int;
|
||||||
final chunksCount = createResponse['chunks_count'] 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
|
// Step 2: Upload chunks
|
||||||
|
int bytesUploaded = 0;
|
||||||
if (fileData is XFile) {
|
if (fileData is XFile) {
|
||||||
// Use stream for XFile
|
// Use stream for XFile
|
||||||
final subscription = fileData.openRead().listen(null);
|
final subscription = fileData.openRead().listen(null);
|
||||||
@@ -171,7 +184,16 @@ class FileUploader {
|
|||||||
for (int i = 0; i < chunksCount; i++) {
|
for (int i = 0; i < chunksCount; i++) {
|
||||||
subscription.resume();
|
subscription.resume();
|
||||||
final chunkData = await _readNextChunk(subscription, chunkSize);
|
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();
|
subscription.cancel();
|
||||||
} else if (fileData is Uint8List) {
|
} else if (fileData is Uint8List) {
|
||||||
@@ -185,13 +207,23 @@ class FileUploader {
|
|||||||
|
|
||||||
// Upload each chunk
|
// Upload each chunk
|
||||||
for (int i = 0; i < chunks.length; i++) {
|
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 {
|
} else {
|
||||||
throw ArgumentError('Invalid fileData type');
|
throw ArgumentError('Invalid fileData type');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 3: Complete upload
|
// Step 3: Complete upload
|
||||||
|
onProgress?.call(null, Duration.zero);
|
||||||
return await completeUpload(taskId);
|
return await completeUpload(taskId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +232,7 @@ class FileUploader {
|
|||||||
required Dio client,
|
required Dio client,
|
||||||
String? poolId,
|
String? poolId,
|
||||||
FileUploadMode? mode,
|
FileUploadMode? mode,
|
||||||
Function(double progress, Duration estimate)? onProgress,
|
Function(double? progress, Duration estimate)? onProgress,
|
||||||
}) {
|
}) {
|
||||||
final completer = Completer<SnCloudFile?>();
|
final completer = Completer<SnCloudFile?>();
|
||||||
|
|
||||||
@@ -266,7 +298,7 @@ class FileUploader {
|
|||||||
UniversalFile fileData,
|
UniversalFile fileData,
|
||||||
Dio client,
|
Dio client,
|
||||||
String? poolId,
|
String? poolId,
|
||||||
Function(double progress, Duration estimate)? onProgress,
|
Function(double? progress, Duration estimate)? onProgress,
|
||||||
Completer<SnCloudFile?> completer,
|
Completer<SnCloudFile?> completer,
|
||||||
) {
|
) {
|
||||||
String actualMimetype = getMimeType(fileData);
|
String actualMimetype = getMimeType(fileData);
|
||||||
@@ -325,23 +357,24 @@ class FileUploader {
|
|||||||
required String contentType,
|
required String contentType,
|
||||||
required Dio client,
|
required Dio client,
|
||||||
String? poolId,
|
String? poolId,
|
||||||
Function(double progress, Duration estimate)? onProgress,
|
Function(double? progress, Duration estimate)? onProgress,
|
||||||
required Completer<SnCloudFile?> completer,
|
required Completer<SnCloudFile?> completer,
|
||||||
}) {
|
}) {
|
||||||
final uploader = FileUploader(client);
|
final uploader = FileUploader(client);
|
||||||
|
|
||||||
// Call progress start
|
// Call progress start
|
||||||
onProgress?.call(0.0, Duration.zero);
|
onProgress?.call(null, Duration.zero);
|
||||||
uploader
|
uploader
|
||||||
.uploadFile(
|
.uploadFile(
|
||||||
fileData: fileData,
|
fileData: fileData,
|
||||||
fileName: fileName,
|
fileName: fileName,
|
||||||
contentType: contentType,
|
contentType: contentType,
|
||||||
poolId: poolId,
|
poolId: poolId,
|
||||||
|
onProgress: onProgress,
|
||||||
)
|
)
|
||||||
.then((result) {
|
.then((result) {
|
||||||
// Call progress end
|
// Call progress end
|
||||||
onProgress?.call(1.0, Duration.zero);
|
onProgress?.call(null, Duration.zero);
|
||||||
completer.complete(result);
|
completer.complete(result);
|
||||||
})
|
})
|
||||||
.catchError((e) {
|
.catchError((e) {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class ChatInput extends HookConsumerWidget {
|
|||||||
final Function(int) onDeleteAttachment;
|
final Function(int) onDeleteAttachment;
|
||||||
final Function(int, int) onMoveAttachment;
|
final Function(int, int) onMoveAttachment;
|
||||||
final Function(List<UniversalFile>) onAttachmentsChanged;
|
final Function(List<UniversalFile>) onAttachmentsChanged;
|
||||||
final Map<String, Map<int, double>> attachmentProgress;
|
final Map<String, Map<int, double?>> attachmentProgress;
|
||||||
|
|
||||||
const ChatInput({
|
const ChatInput({
|
||||||
super.key,
|
super.key,
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class MessageItem extends HookConsumerWidget {
|
|||||||
final LocalChatMessage message;
|
final LocalChatMessage message;
|
||||||
final bool isCurrentUser;
|
final bool isCurrentUser;
|
||||||
final Function(String action)? onAction;
|
final Function(String action)? onAction;
|
||||||
final Map<int, double>? progress;
|
final Map<int, double?>? progress;
|
||||||
final bool showAvatar;
|
final bool showAvatar;
|
||||||
final Function(String messageId) onJump;
|
final Function(String messageId) onJump;
|
||||||
final bool isSelectionMode;
|
final bool isSelectionMode;
|
||||||
@@ -689,7 +689,7 @@ class MessageHoverActionMenu extends StatelessWidget {
|
|||||||
class MessageItemDisplayBubble extends HookConsumerWidget {
|
class MessageItemDisplayBubble extends HookConsumerWidget {
|
||||||
final LocalChatMessage message;
|
final LocalChatMessage message;
|
||||||
final bool isCurrentUser;
|
final bool isCurrentUser;
|
||||||
final Map<int, double>? progress;
|
final Map<int, double?>? progress;
|
||||||
final bool showAvatar;
|
final bool showAvatar;
|
||||||
final Function(String messageId) onJump;
|
final Function(String messageId) onJump;
|
||||||
final String? translatedText;
|
final String? translatedText;
|
||||||
@@ -821,7 +821,7 @@ class MessageItemDisplayBubble extends HookConsumerWidget {
|
|||||||
class MessageItemDisplayIRC extends HookConsumerWidget {
|
class MessageItemDisplayIRC extends HookConsumerWidget {
|
||||||
final LocalChatMessage message;
|
final LocalChatMessage message;
|
||||||
final bool isCurrentUser;
|
final bool isCurrentUser;
|
||||||
final Map<int, double>? progress;
|
final Map<int, double?>? progress;
|
||||||
final bool showAvatar;
|
final bool showAvatar;
|
||||||
final Function(String messageId) onJump;
|
final Function(String messageId) onJump;
|
||||||
final String? translatedText;
|
final String? translatedText;
|
||||||
@@ -949,7 +949,7 @@ class MessageItemDisplayIRC extends HookConsumerWidget {
|
|||||||
class MessageItemDisplayDiscord extends HookConsumerWidget {
|
class MessageItemDisplayDiscord extends HookConsumerWidget {
|
||||||
final LocalChatMessage message;
|
final LocalChatMessage message;
|
||||||
final bool isCurrentUser;
|
final bool isCurrentUser;
|
||||||
final Map<int, double>? progress;
|
final Map<int, double?>? progress;
|
||||||
final bool showAvatar;
|
final bool showAvatar;
|
||||||
final Function(String messageId) onJump;
|
final Function(String messageId) onJump;
|
||||||
final String? translatedText;
|
final String? translatedText;
|
||||||
@@ -1238,7 +1238,7 @@ class MessageQuoteWidget extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class FileUploadProgressWidget extends StatelessWidget {
|
class FileUploadProgressWidget extends StatelessWidget {
|
||||||
final Map<int, double>? progress;
|
final Map<int, double?>? progress;
|
||||||
final Color textColor;
|
final Color textColor;
|
||||||
final bool hasContent;
|
final bool hasContent;
|
||||||
|
|
||||||
@@ -1266,7 +1266,9 @@ class FileUploadProgressWidget extends StatelessWidget {
|
|||||||
'fileUploadingProgress'.tr(
|
'fileUploadingProgress'.tr(
|
||||||
args: [
|
args: [
|
||||||
(entry.key + 1).toString(),
|
(entry.key + 1).toString(),
|
||||||
(entry.value * 100).toStringAsFixed(1),
|
entry.value != null
|
||||||
|
? (entry.value! * 100).toStringAsFixed(1)
|
||||||
|
: '0.0',
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
|||||||
@@ -411,10 +411,7 @@ class AttachmentPreview extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
Gap(6),
|
Gap(6),
|
||||||
Center(
|
Center(
|
||||||
child: LinearProgressIndicator(
|
child: LinearProgressIndicator(value: progress),
|
||||||
value:
|
|
||||||
progress != null ? progress! / 100.0 : null,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ class ArticleComposeAttachments extends ConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
ValueListenableBuilder<Map<int, double>>(
|
ValueListenableBuilder<Map<int, double?>>(
|
||||||
valueListenable: state.attachmentProgress,
|
valueListenable: state.attachmentProgress,
|
||||||
builder: (context, progressMap, _) {
|
builder: (context, progressMap, _) {
|
||||||
return Wrap(
|
return Wrap(
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class ComposeState {
|
|||||||
final TextEditingController slugController;
|
final TextEditingController slugController;
|
||||||
final ValueNotifier<int> visibility;
|
final ValueNotifier<int> visibility;
|
||||||
final ValueNotifier<List<UniversalFile>> attachments;
|
final ValueNotifier<List<UniversalFile>> attachments;
|
||||||
final ValueNotifier<Map<int, double>> attachmentProgress;
|
final ValueNotifier<Map<int, double?>> attachmentProgress;
|
||||||
final ValueNotifier<SnPublisher?> currentPublisher;
|
final ValueNotifier<SnPublisher?> currentPublisher;
|
||||||
final ValueNotifier<bool> submitting;
|
final ValueNotifier<bool> submitting;
|
||||||
final ValueNotifier<List<SnPostCategory>> categories;
|
final ValueNotifier<List<SnPostCategory>> categories;
|
||||||
@@ -520,7 +520,7 @@ class ComposeLogic {
|
|||||||
onProgress: (progress, _) {
|
onProgress: (progress, _) {
|
||||||
state.attachmentProgress.value = {
|
state.attachmentProgress.value = {
|
||||||
...state.attachmentProgress.value,
|
...state.attachmentProgress.value,
|
||||||
index: progress,
|
index: progress ?? 0.0,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
).future;
|
).future;
|
||||||
|
|||||||
@@ -246,7 +246,8 @@ class _ShareSheetState extends ConsumerState<ShareSheet> {
|
|||||||
onProgress: (progress, _) {
|
onProgress: (progress, _) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_fileUploadProgress[messageId]?[idx] = progress;
|
_fileUploadProgress[messageId]?[idx] =
|
||||||
|
progress ?? 0.0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user