Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: file progress debug #6736

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import 'package:appflowy/plugins/database/application/cell/cell_controller_build
import 'package:appflowy/plugins/database/application/field/field_info.dart';
import 'package:appflowy/plugins/database/application/field/type_option/type_option_data_parser.dart';
import 'package:appflowy/plugins/database/application/row/row_service.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/startup/tasks/file_storage_task.dart';
import 'package:appflowy/user/application/user_service.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/log.dart';
Expand All @@ -12,7 +14,9 @@ import 'package:appflowy_backend/protobuf/flowy-database2/file_entities.pbenum.d
import 'package:appflowy_backend/protobuf/flowy-database2/media_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
import 'package:collection/collection.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

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

void Function()? _onCellChangedFn;

Expand All @@ -53,6 +59,8 @@ class MediaCellBloc extends Bloc<MediaCellEvent, MediaCellState> {
(event, emit) async {
await event.when(
initial: () async {
_checkFileStatus();

// Fetch user profile
final userProfileResult =
await UserBackendService.getCurrentUserProfile();
Expand Down Expand Up @@ -96,9 +104,14 @@ class MediaCellBloc extends Bloc<MediaCellEvent, MediaCellState> {
);

final result = await DatabaseEventUpdateMediaCell(payload).send();
result.fold((l) => null, (err) => Log.error(err));
result.fold(
(_) => _registerProgressNotifier(newFile),
(err) => Log.error(err),
);
},
removeFile: (id) async {
_removeNotifier(id);

final payload = MediaCellChangesetPB(
viewId: cellController.viewId,
cellId: CellIdPB(
Expand Down Expand Up @@ -158,6 +171,49 @@ class MediaCellBloc extends Bloc<MediaCellEvent, MediaCellState> {
rowId: cellController.rowId,
cover: cover,
),
onProgressUpdate: (id) {
final FileProgress? progress = _progressNotifiers[id]?.value;
if (progress != null) {
MediaUploadProgress? mediaUploadProgress =
state.uploadProgress.firstWhereOrNull((u) => u.fileId == id);

if (progress.error != null) {
// Remove file from cell
add(MediaCellEvent.removeFile(fileId: id));
_removeNotifier(id);

// Remove progress
final uploadProgress = [...state.uploadProgress];
uploadProgress.removeWhere((u) => u.fileId == id);
emit(state.copyWith(uploadProgress: uploadProgress));
return;
}

if (mediaUploadProgress == null) {
mediaUploadProgress ??= MediaUploadProgress(
fileId: id,
uploadState: progress.progress >= 1
? MediaUploadState.completed
: MediaUploadState.uploading,
fileProgress: progress,
);
} else {
mediaUploadProgress = mediaUploadProgress.copyWith(
uploadState: progress.progress >= 1
? MediaUploadState.completed
: MediaUploadState.uploading,
fileProgress: progress,
);
}

final uploadProgress = [...state.uploadProgress];
uploadProgress
..removeWhere((u) => u.fileId == id)
..add(mediaUploadProgress);

emit(state.copyWith(uploadProgress: uploadProgress));
}
},
);
},
);
Expand All @@ -174,6 +230,53 @@ class MediaCellBloc extends Bloc<MediaCellEvent, MediaCellState> {
);
}

/// We check the file state of all the files that are in Cloud (hosted by us) in the cell.
///
/// If any file has failed, we should notify the user about it,
/// and also remove it from the cell.
///
/// This method registers the progress notifiers for each file.
///
void _checkFileStatus() {
for (final file in state.files) {
_registerProgressNotifier(file);
}
}

void _registerProgressNotifier(MediaFilePB file) {
if (file.uploadType != FileUploadTypePB.CloudFile) {
return;
}

final notifier = _fileStorageService.onFileProgress(fileUrl: file.url);
_progressNotifiers[file.id] = notifier;
notifier.addListener(() => _onProgressChanged(file.id));

add(MediaCellEvent.onProgressUpdate(file.id));
}

void _onProgressChanged(String id) {
// Ignore events if the file is already uploaded
final progress =
state.uploadProgress.firstWhereOrNull((u) => u.fileId == id);
if (progress == null ||
progress.uploadState == MediaUploadState.completed) {
return;
}

add(MediaCellEvent.onProgressUpdate(id));
}

/// Removes and disposes of a progress notifier if found
///
void _removeNotifier(String id) {
SchedulerBinding.instance.addPostFrameCallback((_) {
final notifier = _progressNotifiers.remove(id);
notifier?.removeListener(() => _onProgressChanged(id));
notifier?.dispose();
});
}

void _onFieldChangedListener(FieldInfo fieldInfo) {
if (!isClosed) {
add(MediaCellEvent.didUpdateField(fieldInfo.name));
Expand Down Expand Up @@ -221,6 +324,9 @@ class MediaCellEvent with _$MediaCellEvent {
const factory MediaCellEvent.toggleShowAllFiles() = _ToggleShowAllFiles;

const factory MediaCellEvent.setCover(RowCoverPB cover) = _SetCover;

const factory MediaCellEvent.onProgressUpdate(String fileId) =
_OnProgressUpdate;
}

@freezed
Expand All @@ -231,6 +337,7 @@ class MediaCellState with _$MediaCellState {
@Default([]) List<MediaFilePB> files,
@Default(false) showAllFiles,
@Default(true) hideFileNames,
@Default([]) List<MediaUploadProgress> uploadProgress,
}) = _MediaCellState;

factory MediaCellState.initial(MediaCellController cellController) {
Expand All @@ -245,3 +352,32 @@ class MediaCellState with _$MediaCellState {
);
}
}

enum MediaUploadState { uploading, completed }

class MediaUploadProgress {
const MediaUploadProgress({
required this.fileId,
required this.uploadState,
required this.fileProgress,
});

final String fileId;
final MediaUploadState uploadState;
final FileProgress fileProgress;

@override
String toString() =>
'MediaUploadProgress(fileId: $fileId, uploadState: $uploadState, fileProgress: $fileProgress)';

MediaUploadProgress copyWith({
MediaUploadState? uploadState,
FileProgress? fileProgress,
}) {
return MediaUploadProgress(
fileId: fileId,
uploadState: uploadState ?? this.uploadState,
fileProgress: fileProgress ?? this.fileProgress,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,30 @@ class GridMediaCellSkin extends IEditableMediaCellSkin {
Widget child = BlocBuilder<MediaCellBloc, MediaCellState>(
builder: (context, state) {
final wrapContent = context.read<MediaCellBloc>().wrapContent;
final List<Widget> children = state.files
.map<Widget>(
(file) => GestureDetector(
onTap: () => _openOrExpandFile(context, file, state.files),
child: Padding(
padding: wrapContent
? const EdgeInsets.only(right: 4)
: EdgeInsets.zero,
child: _FilePreviewRender(file: file),
final List<Widget> children = state.files.map<Widget>(
(file) {
return GestureDetector(
onTap: () {
// TODO(Nathan): Add retry event if file upload failed
// if (uploadFailed) {
// return add_event_here;
// }

_openOrExpandFile(context, file, state.files);
},
child: Padding(
padding: wrapContent
? const EdgeInsets.only(right: 4)
: EdgeInsets.zero,
child: _FilePreviewRender(
file: file,
// TODO(Nathan): Add error value for this file
didError: false,
),
),
)
.toList();
);
},
).toList();

if (isMobileRowDetail && state.files.isEmpty) {
children.add(
Expand Down Expand Up @@ -221,29 +232,36 @@ class GridMediaCellSkin extends IEditableMediaCellSkin {
}

class _FilePreviewRender extends StatelessWidget {
const _FilePreviewRender({required this.file});
const _FilePreviewRender({required this.file, this.didError = false});

final MediaFilePB file;
final bool didError;

@override
Widget build(BuildContext context) {
if (file.fileType != MediaFileTypePB.Image) {
if (file.fileType != MediaFileTypePB.Image || didError) {
return FlowyTooltip(
message: file.name,
message: didError ? LocaleKeys.grid_media_uploadFailed.tr() : file.name,
child: Container(
height: 28,
width: 28,
clipBehavior: Clip.antiAlias,
padding: const EdgeInsets.all(8),
padding: EdgeInsets.all(didError ? 6 : 8),
decoration: BoxDecoration(
color: AFThemeExtension.of(context).greyHover,
borderRadius: BorderRadius.circular(4),
),
child: FlowySvg(
file.fileType.icon,
size: const Size.square(12),
color: AFThemeExtension.of(context).textColor,
),
child: didError
? const FlowySvg(
FlowySvgs.notice_s,
size: Size.square(16),
blendMode: BlendMode.dstIn,
)
: FlowySvg(
file.fileType.icon,
size: const Size.square(12),
color: AFThemeExtension.of(context).textColor,
),
),
);
}
Expand Down
Loading
Loading