♻️ Better task overlay progress

This commit is contained in:
2026-01-11 13:11:15 +08:00
parent bf59108569
commit 88c4d648d5
2 changed files with 196 additions and 94 deletions

View File

@@ -256,6 +256,21 @@ class UploadTasksNotifier extends Notifier<List<DriveTask>> {
}).toList(); }).toList();
} }
void updateUploadProgress(String taskId, int uploadedBytes, int uploadedChunks) {
state =
state.map((task) {
if (task.taskId == taskId) {
return task.copyWith(
uploadedBytes: uploadedBytes,
uploadedChunks: uploadedChunks,
status: DriveTaskStatus.inProgress,
updatedAt: DateTime.now(),
);
}
return task;
}).toList();
}
void updateDownloadProgress( void updateDownloadProgress(
String taskId, String taskId,
int downloadedBytes, int downloadedBytes,
@@ -470,6 +485,7 @@ class EnhancedFileUploader extends FileUploader {
// Step 2: Upload chunks // Step 2: Upload chunks
int bytesUploaded = 0; int bytesUploaded = 0;
int chunksUploaded = 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);
@@ -494,6 +510,11 @@ class EnhancedFileUploader extends FileUploader {
}, },
); );
bytesUploaded += chunkData.length; bytesUploaded += chunkData.length;
chunksUploaded += 1;
// Update upload progress in UI
ref
.read(uploadTasksProvider.notifier)
.updateUploadProgress(taskId, bytesUploaded, chunksUploaded);
} }
subscription.cancel(); subscription.cancel();
} else if (fileData is Uint8List) { } else if (fileData is Uint8List) {
@@ -521,6 +542,11 @@ class EnhancedFileUploader extends FileUploader {
}, },
); );
bytesUploaded += chunks[i].length; bytesUploaded += chunks[i].length;
chunksUploaded += 1;
// Update upload progress in UI
ref
.read(uploadTasksProvider.notifier)
.updateUploadProgress(taskId, bytesUploaded, chunksUploaded);
} }
} else { } else {
throw ArgumentError('Invalid fileData type'); throw ArgumentError('Invalid fileData type');

View File

@@ -251,7 +251,9 @@ class _TaskOverlayContent extends HookConsumerWidget {
style: Theme.of(context).textTheme.bodySmall style: Theme.of(context).textTheme.bodySmall
?.copyWith( ?.copyWith(
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurface, color: Theme.of(
context,
).colorScheme.onSurface,
), ),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@@ -260,7 +262,10 @@ class _TaskOverlayContent extends HookConsumerWidget {
SizedBox( SizedBox(
width: 16, width: 16,
height: 16, height: 16,
child: CircularProgressIndicator( child: Stack(
alignment: Alignment.center,
children: [
CircularProgressIndicator(
value: _getOverallProgress(activeTasks), value: _getOverallProgress(activeTasks),
strokeWidth: 3, strokeWidth: 3,
backgroundColor: Theme.of( backgroundColor: Theme.of(
@@ -268,6 +273,24 @@ class _TaskOverlayContent extends HookConsumerWidget {
).colorScheme.surfaceContainerHighest, ).colorScheme.surfaceContainerHighest,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
), ),
if (activeTasks.any(
(task) =>
task.status == DriveTaskStatus.inProgress,
))
CircularProgressIndicator(
value: null, // Indeterminate
strokeWidth: 3,
trackGap: 0,
valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(
context,
).colorScheme.secondary.withOpacity(0.5),
),
backgroundColor: Colors.transparent,
padding: EdgeInsets.zero,
),
],
),
), ),
], ],
).padding(horizontal: 12), ).padding(horizontal: 12),
@@ -316,7 +339,9 @@ class _TaskOverlayContent extends HookConsumerWidget {
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.titleSmall .titleSmall
?.copyWith(fontWeight: FontWeight.w600), ?.copyWith(
fontWeight: FontWeight.w600,
),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@@ -341,13 +366,35 @@ class _TaskOverlayContent extends HookConsumerWidget {
SizedBox( SizedBox(
width: 32, width: 32,
height: 32, height: 32,
child: CircularProgressIndicator( child: Stack(
children: [
CircularProgressIndicator(
value: _getOverallProgress(activeTasks), value: _getOverallProgress(activeTasks),
strokeWidth: 3, strokeWidth: 3,
backgroundColor: Theme.of( backgroundColor: Theme.of(
context, context,
).colorScheme.surfaceContainerHighest, ).colorScheme.surfaceContainerHighest,
), ),
if (activeTasks.any(
(task) =>
task.status ==
DriveTaskStatus.inProgress,
))
CircularProgressIndicator(
value: null, // Indeterminate
strokeWidth: 3,
trackGap: 0,
valueColor:
AlwaysStoppedAnimation<Color>(
Theme.of(context)
.colorScheme
.secondary
.withOpacity(0.5),
),
backgroundColor: Colors.transparent,
),
],
),
), ),
// Expand/collapse button // Expand/collapse button
@@ -378,13 +425,17 @@ class _TaskOverlayContent extends HookConsumerWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border( border: Border(
top: BorderSide( top: BorderSide(
color: Theme.of(context).colorScheme.outline, color: Theme.of(
context,
).colorScheme.outline,
width: width:
1 / 1 /
MediaQuery.of(context).devicePixelRatio, MediaQuery.of(context).devicePixelRatio,
), ),
), ),
), ),
child: Material(
color: Colors.transparent,
child: CustomScrollView( child: CustomScrollView(
slivers: [ slivers: [
// Clear completed tasks button // Clear completed tasks button
@@ -392,7 +443,9 @@ class _TaskOverlayContent extends HookConsumerWidget {
SliverToBoxAdapter( SliverToBoxAdapter(
child: ListTile( child: ListTile(
dense: true, dense: true,
title: const Text('clearCompleted').tr(), title: const Text(
'clearCompleted',
).tr(),
leading: Icon( leading: Icon(
Symbols.clear_all, Symbols.clear_all,
size: 18, size: 18,
@@ -414,7 +467,8 @@ class _TaskOverlayContent extends HookConsumerWidget {
// Clear all tasks button // Clear all tasks button
if (activeTasks.any( if (activeTasks.any(
(task) => (task) =>
task.status != DriveTaskStatus.completed, task.status !=
DriveTaskStatus.completed,
)) ))
SliverToBoxAdapter( SliverToBoxAdapter(
child: ListTile( child: ListTile(
@@ -457,6 +511,7 @@ class _TaskOverlayContent extends HookConsumerWidget {
), ),
), ),
), ),
),
], ],
), ),
), ),
@@ -488,19 +543,30 @@ class _TaskOverlayContent extends HookConsumerWidget {
); );
} }
double? _getTaskProgress(DriveTask task) {
if (task.status == DriveTaskStatus.completed) return 1.0;
if (task.status != DriveTaskStatus.inProgress) return 0.0;
// If all bytes are uploaded but still in progress, show indeterminate
if (task.uploadedBytes >= task.fileSize && task.fileSize > 0) {
return null; // Indeterminate progress
}
return task.fileSize > 0 ? task.uploadedBytes / task.fileSize : 0.0;
}
double _getOverallProgress(List<DriveTask> tasks) { double _getOverallProgress(List<DriveTask> tasks) {
if (tasks.isEmpty) return 0.0; if (tasks.isEmpty) return 0.0;
final totalProgress = tasks.fold<double>(0.0, (sum, task) {
final byteProgress = task.fileSize > 0 final progressValues = tasks.map((task) => _getTaskProgress(task));
? task.uploadedBytes / task.fileSize final determinateProgresses = progressValues.where((p) => p != null);
: 0.0;
return sum + if (determinateProgresses.isEmpty) return 0.0;
(task.status == DriveTaskStatus.inProgress
? byteProgress final totalProgress = determinateProgresses.fold<double>(
: task.status == DriveTaskStatus.completed 0.0,
? 1 (sum, progress) => sum + progress!,
: 0); );
});
return totalProgress / tasks.length; return totalProgress / tasks.length;
} }
@@ -625,6 +691,18 @@ class UploadTaskTile extends StatefulWidget {
@override @override
State<UploadTaskTile> createState() => _UploadTaskTileState(); State<UploadTaskTile> createState() => _UploadTaskTileState();
static double? _getTaskProgress(DriveTask task) {
if (task.status == DriveTaskStatus.completed) return 1.0;
if (task.status == DriveTaskStatus.inProgress) return null;
// If all bytes are uploaded but still in progress, show indeterminate
if (task.uploadedBytes >= task.fileSize && task.fileSize > 0) {
return null; // Indeterminate progress
}
return task.fileSize > 0 ? task.uploadedBytes / task.fileSize : 0.0;
}
} }
class _UploadTaskTileState extends State<UploadTaskTile> class _UploadTaskTileState extends State<UploadTaskTile>
@@ -685,9 +763,7 @@ class _UploadTaskTileState extends State<UploadTaskTile>
child: Padding( child: Padding(
padding: const EdgeInsets.all(2), padding: const EdgeInsets.all(2),
child: CircularProgressIndicator( child: CircularProgressIndicator(
value: widget.task.fileSize > 0 value: UploadTaskTile._getTaskProgress(widget.task),
? widget.task.uploadedBytes / widget.task.fileSize
: 0.0,
strokeWidth: 2.5, strokeWidth: 2.5,
backgroundColor: Theme.of( backgroundColor: Theme.of(
context, context,
@@ -814,7 +890,7 @@ class _UploadTaskTileState extends State<UploadTaskTile>
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
LinearProgressIndicator( LinearProgressIndicator(
value: widget.task.progress, value: UploadTaskTile._getTaskProgress(widget.task),
backgroundColor: Theme.of(context).colorScheme.surface, backgroundColor: Theme.of(context).colorScheme.surface,
valueColor: AlwaysStoppedAnimation<Color>( valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).colorScheme.primary, Theme.of(context).colorScheme.primary,