Skip to content

Commit 290790e

Browse files
committed
feat: file progress debug
1 parent 193c824 commit 290790e

File tree

3 files changed

+152
-17
lines changed

3 files changed

+152
-17
lines changed

frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/media_cell_bloc.dart

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import 'package:appflowy/plugins/database/application/cell/cell_controller_build
44
import 'package:appflowy/plugins/database/application/field/field_info.dart';
55
import 'package:appflowy/plugins/database/application/field/type_option/type_option_data_parser.dart';
66
import 'package:appflowy/plugins/database/application/row/row_service.dart';
7+
import 'package:appflowy/startup/startup.dart';
8+
import 'package:appflowy/startup/tasks/file_storage_task.dart';
79
import 'package:appflowy/user/application/user_service.dart';
810
import 'package:appflowy_backend/dispatch/dispatch.dart';
911
import 'package:appflowy_backend/log.dart';
@@ -12,7 +14,9 @@ import 'package:appflowy_backend/protobuf/flowy-database2/file_entities.pbenum.d
1214
import 'package:appflowy_backend/protobuf/flowy-database2/media_entities.pb.dart';
1315
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
1416
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
17+
import 'package:collection/collection.dart';
1518
import 'package:flowy_infra/uuid.dart';
19+
import 'package:flutter/scheduler.dart';
1620
import 'package:flutter_bloc/flutter_bloc.dart';
1721
import 'package:freezed_annotation/freezed_annotation.dart';
1822

@@ -29,6 +33,8 @@ class MediaCellBloc extends Bloc<MediaCellEvent, MediaCellState> {
2933
late final RowBackendService _rowService =
3034
RowBackendService(viewId: cellController.viewId);
3135
final MediaCellController cellController;
36+
final FileStorageService _fileStorageService = getIt<FileStorageService>();
37+
final Map<String, AutoRemoveNotifier<FileProgress>> _progressNotifiers = {};
3238

3339
void Function()? _onCellChangedFn;
3440

@@ -53,6 +59,8 @@ class MediaCellBloc extends Bloc<MediaCellEvent, MediaCellState> {
5359
(event, emit) async {
5460
await event.when(
5561
initial: () async {
62+
_checkFileStatus();
63+
5664
// Fetch user profile
5765
final userProfileResult =
5866
await UserBackendService.getCurrentUserProfile();
@@ -96,9 +104,14 @@ class MediaCellBloc extends Bloc<MediaCellEvent, MediaCellState> {
96104
);
97105

98106
final result = await DatabaseEventUpdateMediaCell(payload).send();
99-
result.fold((l) => null, (err) => Log.error(err));
107+
result.fold(
108+
(_) => _registerProgressNotifier(newFile),
109+
(err) => Log.error(err),
110+
);
100111
},
101112
removeFile: (id) async {
113+
_removeNotifier(id);
114+
102115
final payload = MediaCellChangesetPB(
103116
viewId: cellController.viewId,
104117
cellId: CellIdPB(
@@ -158,6 +171,40 @@ class MediaCellBloc extends Bloc<MediaCellEvent, MediaCellState> {
158171
rowId: cellController.rowId,
159172
cover: cover,
160173
),
174+
onProgressUpdate: (id) {
175+
final FileProgress? progress = _progressNotifiers[id]?.value;
176+
if (progress != null) {
177+
MediaUploadProgress? mediaUploadProgress =
178+
state.uploadProgress.firstWhereOrNull((u) => u.fileId == id);
179+
180+
if (progress.error != null) {
181+
// Remove file from cell
182+
add(MediaCellEvent.removeFile(fileId: id));
183+
_removeNotifier(id);
184+
185+
// Remove progress
186+
final uploadProgress = [...state.uploadProgress];
187+
uploadProgress.removeWhere((u) => u.fileId == id);
188+
emit(state.copyWith(uploadProgress: uploadProgress));
189+
return;
190+
}
191+
192+
mediaUploadProgress ??= MediaUploadProgress(
193+
fileId: id,
194+
uploadState: progress.progress >= 1
195+
? MediaUploadState.completed
196+
: MediaUploadState.uploading,
197+
fileProgress: progress,
198+
);
199+
200+
final uploadProgress = [...state.uploadProgress];
201+
uploadProgress
202+
..removeWhere((u) => u.fileId == id)
203+
..add(mediaUploadProgress);
204+
205+
emit(state.copyWith(uploadProgress: uploadProgress));
206+
}
207+
},
161208
);
162209
},
163210
);
@@ -174,6 +221,44 @@ class MediaCellBloc extends Bloc<MediaCellEvent, MediaCellState> {
174221
);
175222
}
176223

224+
/// We check the file state of all the files that are in Cloud (hosted by us) in the cell.
225+
///
226+
/// If any file has failed, we should notify the user about it,
227+
/// and also remove it from the cell.
228+
///
229+
/// This method registers the progress notifiers for each file.
230+
///
231+
void _checkFileStatus() {
232+
for (final file in state.files) {
233+
_registerProgressNotifier(file);
234+
}
235+
}
236+
237+
void _registerProgressNotifier(MediaFilePB file) {
238+
if (file.uploadType != FileUploadTypePB.CloudFile) {
239+
return;
240+
}
241+
242+
final notifier = _fileStorageService.onFileProgress(fileUrl: file.url);
243+
_progressNotifiers[file.id] = notifier;
244+
notifier.addListener(() => _onProgressChanged(file.id));
245+
246+
add(MediaCellEvent.onProgressUpdate(file.id));
247+
}
248+
249+
void _onProgressChanged(String id) =>
250+
add(MediaCellEvent.onProgressUpdate(id));
251+
252+
/// Removes and disposes of a progress notifier if found
253+
///
254+
void _removeNotifier(String id) {
255+
SchedulerBinding.instance.addPostFrameCallback((_) {
256+
final notifier = _progressNotifiers.remove(id);
257+
notifier?.removeListener(() => _onProgressChanged(id));
258+
notifier?.dispose();
259+
});
260+
}
261+
177262
void _onFieldChangedListener(FieldInfo fieldInfo) {
178263
if (!isClosed) {
179264
add(MediaCellEvent.didUpdateField(fieldInfo.name));
@@ -221,6 +306,9 @@ class MediaCellEvent with _$MediaCellEvent {
221306
const factory MediaCellEvent.toggleShowAllFiles() = _ToggleShowAllFiles;
222307

223308
const factory MediaCellEvent.setCover(RowCoverPB cover) = _SetCover;
309+
310+
const factory MediaCellEvent.onProgressUpdate(String fileId) =
311+
_OnProgressUpdate;
224312
}
225313

226314
@freezed
@@ -231,6 +319,7 @@ class MediaCellState with _$MediaCellState {
231319
@Default([]) List<MediaFilePB> files,
232320
@Default(false) showAllFiles,
233321
@Default(true) hideFileNames,
322+
@Default([]) List<MediaUploadProgress> uploadProgress,
234323
}) = _MediaCellState;
235324

236325
factory MediaCellState.initial(MediaCellController cellController) {
@@ -245,3 +334,17 @@ class MediaCellState with _$MediaCellState {
245334
);
246335
}
247336
}
337+
338+
enum MediaUploadState { uploading, completed }
339+
340+
class MediaUploadProgress {
341+
const MediaUploadProgress({
342+
required this.fileId,
343+
required this.uploadState,
344+
required this.fileProgress,
345+
});
346+
347+
final String fileId;
348+
final MediaUploadState uploadState;
349+
final FileProgress fileProgress;
350+
}

frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_media_cell.dart

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_media_upl
33
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/common.dart';
44
import 'package:appflowy/workspace/presentation/widgets/image_viewer/image_provider.dart';
55
import 'package:appflowy/workspace/presentation/widgets/image_viewer/interactive_image_viewer.dart';
6+
import 'package:collection/collection.dart';
67
import 'package:flutter/material.dart';
78

89
import 'package:appflowy/generated/flowy_svgs.g.dart';
@@ -42,19 +43,25 @@ class GridMediaCellSkin extends IEditableMediaCellSkin {
4243
Widget child = BlocBuilder<MediaCellBloc, MediaCellState>(
4344
builder: (context, state) {
4445
final wrapContent = context.read<MediaCellBloc>().wrapContent;
45-
final List<Widget> children = state.files
46-
.map<Widget>(
47-
(file) => GestureDetector(
48-
onTap: () => _openOrExpandFile(context, file, state.files),
49-
child: Padding(
50-
padding: wrapContent
51-
? const EdgeInsets.only(right: 4)
52-
: EdgeInsets.zero,
53-
child: _FilePreviewRender(file: file),
46+
final List<Widget> children = state.files.map<Widget>(
47+
(file) {
48+
final fileUploadProgress = state.uploadProgress
49+
.firstWhereOrNull((u) => u.fileId == file.id);
50+
51+
return GestureDetector(
52+
onTap: () => _openOrExpandFile(context, file, state.files),
53+
child: Padding(
54+
padding: wrapContent
55+
? const EdgeInsets.only(right: 4)
56+
: EdgeInsets.zero,
57+
child: _FilePreviewRender(
58+
file: file,
59+
progress: fileUploadProgress,
5460
),
5561
),
56-
)
57-
.toList();
62+
);
63+
},
64+
).toList();
5865

5966
if (isMobileRowDetail && state.files.isEmpty) {
6067
children.add(
@@ -221,12 +228,36 @@ class GridMediaCellSkin extends IEditableMediaCellSkin {
221228
}
222229

223230
class _FilePreviewRender extends StatelessWidget {
224-
const _FilePreviewRender({required this.file});
231+
const _FilePreviewRender({required this.file, this.progress});
225232

226233
final MediaFilePB file;
234+
final MediaUploadProgress? progress;
227235

228236
@override
229237
Widget build(BuildContext context) {
238+
if (progress != null &&
239+
progress!.uploadState != MediaUploadState.completed) {
240+
return Container(
241+
margin: const EdgeInsets.symmetric(vertical: 2),
242+
height: 24,
243+
width: 24,
244+
child: Stack(
245+
children: [
246+
CircularProgressIndicator(
247+
value: progress!.fileProgress.progress,
248+
strokeWidth: 1.5,
249+
),
250+
Center(
251+
child: FlowyText(
252+
'${progress!.fileProgress.progress.floor() * 100}%',
253+
fontSize: 8,
254+
),
255+
),
256+
],
257+
),
258+
);
259+
}
260+
230261
if (file.fileType != MediaFileTypePB.Image) {
231262
return FlowyTooltip(
232263
message: file.name,

frontend/appflowy_flutter/lib/startup/tasks/file_storage_task.dart

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,7 @@ class FileStorageTask extends LaunchTask {
1919

2020
@override
2121
Future<void> initialize(LaunchContext context) async {
22-
context.getIt.registerSingleton(
23-
FileStorageService(),
24-
dispose: (service) async => service.dispose(),
25-
);
22+
context.getIt.registerSingleton(FileStorageService());
2623
}
2724

2825
@override
@@ -131,6 +128,10 @@ class FileProgress {
131128
final double progress;
132129
final String fileUrl;
133130
final String? error;
131+
132+
@override
133+
String toString() =>
134+
'FileProgress(progress: $progress, fileUrl: $fileUrl, error: $error)';
134135
}
135136

136137
class AutoRemoveNotifier<T> extends ValueNotifier<T> {

0 commit comments

Comments
 (0)