Improve FileSharerDialog to match the deprecated one in HomePhotos

This commit is contained in:
Ming Ming 2023-12-26 17:47:41 +08:00
parent 782f73448e
commit 21ac3c5530
5 changed files with 149 additions and 44 deletions

View file

@ -27,7 +27,7 @@ class DownloadProgressDialog extends StatelessWidget {
Align( Align(
alignment: AlignmentDirectional.centerEnd, alignment: AlignmentDirectional.centerEnd,
child: Text( child: Text(
"$current/$max", "${current + 1}/$max",
style: Theme.of(context).textTheme.labelMedium, style: Theme.of(context).textTheme.labelMedium,
), ),
), ),

View file

@ -15,10 +15,12 @@ import 'package:nc_photos/di_container.dart';
import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/file.dart';
import 'package:nc_photos/entity/file_descriptor.dart'; import 'package:nc_photos/entity/file_descriptor.dart';
import 'package:nc_photos/entity/file_util.dart' as file_util; import 'package:nc_photos/entity/file_util.dart' as file_util;
import 'package:nc_photos/exception.dart';
import 'package:nc_photos/exception_event.dart'; import 'package:nc_photos/exception_event.dart';
import 'package:nc_photos/exception_util.dart' as exception_util; import 'package:nc_photos/exception_util.dart' as exception_util;
import 'package:nc_photos/k.dart' as k; import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/mobile/share.dart'; import 'package:nc_photos/mobile/share.dart';
import 'package:nc_photos/platform/download.dart';
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util; import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
import 'package:nc_photos/toast.dart'; import 'package:nc_photos/toast.dart';
import 'package:nc_photos/use_case/copy.dart'; import 'package:nc_photos/use_case/copy.dart';
@ -27,6 +29,7 @@ import 'package:nc_photos/use_case/create_share.dart';
import 'package:nc_photos/use_case/download_file.dart'; import 'package:nc_photos/use_case/download_file.dart';
import 'package:nc_photos/use_case/download_preview.dart'; import 'package:nc_photos/use_case/download_preview.dart';
import 'package:nc_photos/use_case/inflate_file_descriptor.dart'; import 'package:nc_photos/use_case/inflate_file_descriptor.dart';
import 'package:nc_photos/widget/download_progress_dialog.dart';
import 'package:nc_photos/widget/processing_dialog.dart'; import 'package:nc_photos/widget/processing_dialog.dart';
import 'package:nc_photos/widget/share_link_multiple_files_dialog.dart'; import 'package:nc_photos/widget/share_link_multiple_files_dialog.dart';
import 'package:nc_photos/widget/simple_input_dialog.dart'; import 'package:nc_photos/widget/simple_input_dialog.dart';
@ -42,8 +45,6 @@ part 'file_sharer_dialog/bloc.dart';
part 'file_sharer_dialog/state_event.dart'; part 'file_sharer_dialog/state_event.dart';
part 'file_sharer_dialog/type.dart'; part 'file_sharer_dialog/type.dart';
typedef _BlocBuilder = BlocBuilder<_Bloc, _State>;
/// Dialog to let user share files with different options /// Dialog to let user share files with different options
/// ///
/// Return true if the files are actually shared, false if user cancelled or /// Return true if the files are actually shared, false if user cancelled or
@ -78,7 +79,7 @@ class _WrappedFileSharerDialog extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiBlocListener( return MultiBlocListener(
listeners: [ listeners: [
BlocListener<_Bloc, _State>( _BlocListener(
listenWhen: (previous, current) => previous.error != current.error, listenWhen: (previous, current) => previous.error != current.error,
listener: (context, state) { listener: (context, state) {
if (state.error != null) { if (state.error != null) {
@ -98,7 +99,7 @@ class _WrappedFileSharerDialog extends StatelessWidget {
} }
}, },
), ),
BlocListener<_Bloc, _State>( _BlocListener(
listenWhen: (previous, current) => listenWhen: (previous, current) =>
previous.message != current.message, previous.message != current.message,
listener: (context, state) { listener: (context, state) {
@ -111,7 +112,7 @@ class _WrappedFileSharerDialog extends StatelessWidget {
} }
}, },
), ),
BlocListener<_Bloc, _State>( _BlocListener(
listenWhen: (previous, current) => previous.result != current.result, listenWhen: (previous, current) => previous.result != current.result,
listener: (context, state) { listener: (context, state) {
if (state.result != null) { if (state.result != null) {
@ -146,10 +147,8 @@ class _ShareMethodDialog extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isSupportPerview = context final isSupportPerview =
.read<_Bloc>() context.bloc.files.any((f) => file_util.isSupportedImageFormat(f));
.files
.any((f) => file_util.isSupportedImageFormat(f));
return SimpleDialog( return SimpleDialog(
title: Text(L10n.global().shareMethodDialogTitle), title: Text(L10n.global().shareMethodDialogTitle),
children: [ children: [
@ -161,9 +160,7 @@ class _ShareMethodDialog extends StatelessWidget {
subtitle: Text(L10n.global().shareMethodPreviewDescription), subtitle: Text(L10n.global().shareMethodPreviewDescription),
), ),
onPressed: () { onPressed: () {
context context.addEvent(const _SetMethod(ShareMethod.preview));
.read<_Bloc>()
.add(const _SetMethod(ShareMethod.preview));
}, },
), ),
SimpleDialogOption( SimpleDialogOption(
@ -172,7 +169,7 @@ class _ShareMethodDialog extends StatelessWidget {
subtitle: Text(L10n.global().shareMethodOriginalFileDescription), subtitle: Text(L10n.global().shareMethodOriginalFileDescription),
), ),
onPressed: () { onPressed: () {
context.read<_Bloc>().add(const _SetMethod(ShareMethod.file)); context.addEvent(const _SetMethod(ShareMethod.file));
}, },
), ),
], ],
@ -182,7 +179,7 @@ class _ShareMethodDialog extends StatelessWidget {
subtitle: Text(L10n.global().shareMethodPublicLinkDescription), subtitle: Text(L10n.global().shareMethodPublicLinkDescription),
), ),
onPressed: () { onPressed: () {
context.read<_Bloc>().add(const _SetMethod(ShareMethod.publicLink)); context.addEvent(const _SetMethod(ShareMethod.publicLink));
}, },
), ),
SimpleDialogOption( SimpleDialogOption(
@ -191,9 +188,7 @@ class _ShareMethodDialog extends StatelessWidget {
subtitle: Text(L10n.global().shareMethodPasswordLinkDescription), subtitle: Text(L10n.global().shareMethodPasswordLinkDescription),
), ),
onPressed: () { onPressed: () {
context context.addEvent(const _SetMethod(ShareMethod.passwordLink));
.read<_Bloc>()
.add(const _SetMethod(ShareMethod.passwordLink));
}, },
), ),
], ],
@ -206,18 +201,24 @@ class _ShareFileDialog extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return _BlocBuilder( return _BlocSelector<_FileState?>(
buildWhen: (previous, current) => selector: (state) => state.fileState,
previous.previewState?.index != current.previewState?.index || builder: (context, fileState) {
previous.previewState?.count != current.previewState?.count, if (fileState != null) {
builder: (context, state) { return DownloadProgressDialog(
final text = state.previewState?.index != null && max: fileState.count,
state.previewState?.count != null current: fileState.index,
? " (${state.previewState!.index}/${state.previewState!.count})" progress: fileState.progress,
: ""; label: context.bloc.files[fileState.index].filename,
return ProcessingDialog( onCancel: () {
text: L10n.global().shareDownloadingDialogContent + text, context.addEvent(const _CancelFileDownload());
); },
);
} else {
return ProcessingDialog(
text: L10n.global().genericProcessingDialogContent,
);
}
}, },
); );
} }
@ -288,7 +289,7 @@ class _SharePublicLinkDialogState extends State<_SharePublicLinkDialog> {
} }
} }
late final _bloc = context.read<_Bloc>(); late final _bloc = context.bloc;
} }
class _SharePasswordLinkDialog extends StatefulWidget { class _SharePasswordLinkDialog extends StatefulWidget {
@ -368,5 +369,15 @@ class _SharePasswordLinkDialogState extends State<_SharePasswordLinkDialog> {
} }
} }
late final _bloc = context.read<_Bloc>(); late final _bloc = context.bloc;
}
typedef _BlocBuilder = BlocBuilder<_Bloc, _State>;
typedef _BlocListener = BlocListener<_Bloc, _State>;
typedef _BlocSelector<T> = BlocSelector<_Bloc, _State, T>;
extension on BuildContext {
_Bloc get bloc => read<_Bloc>();
// _State get state => bloc.state;
void addEvent(_Event event) => bloc.add(event);
} }

View file

@ -88,16 +88,32 @@ extension $_PreviewStateCopyWith on _PreviewState {
} }
abstract class $_FileStateCopyWithWorker { abstract class $_FileStateCopyWithWorker {
_FileState call({int? index, int? count}); _FileState call(
{int? index,
double? progress,
int? count,
Download? download,
bool? shouldRun});
} }
class _$_FileStateCopyWithWorkerImpl implements $_FileStateCopyWithWorker { class _$_FileStateCopyWithWorkerImpl implements $_FileStateCopyWithWorker {
_$_FileStateCopyWithWorkerImpl(this.that); _$_FileStateCopyWithWorkerImpl(this.that);
@override @override
_FileState call({dynamic index, dynamic count}) { _FileState call(
{dynamic index,
dynamic progress = copyWithNull,
dynamic count,
dynamic download = copyWithNull,
dynamic shouldRun}) {
return _FileState( return _FileState(
index: index as int? ?? that.index, count: count as int? ?? that.count); index: index as int? ?? that.index,
progress:
progress == copyWithNull ? that.progress : progress as double?,
count: count as int? ?? that.count,
download:
download == copyWithNull ? that.download : download as Download?,
shouldRun: shouldRun as bool? ?? that.shouldRun);
} }
final _FileState that; final _FileState that;
@ -165,7 +181,7 @@ extension _$_PreviewStateToString on _PreviewState {
extension _$_FileStateToString on _FileState { extension _$_FileStateToString on _FileState {
String _$toString() { String _$toString() {
// ignore: unnecessary_string_interpolations // ignore: unnecessary_string_interpolations
return "_FileState {index: $index, count: $count}"; return "_FileState {index: $index, progress: ${progress == null ? null : "${progress!.toStringAsFixed(3)}"}, count: $count, download: $download, shouldRun: $shouldRun}";
} }
} }
@ -197,6 +213,13 @@ extension _$_SetResultToString on _SetResult {
} }
} }
extension _$_CancelFileDownloadToString on _CancelFileDownload {
String _$toString() {
// ignore: unnecessary_string_interpolations
return "_CancelFileDownload {}";
}
}
extension _$_SetPublicLinkDetailsToString on _SetPublicLinkDetails { extension _$_SetPublicLinkDetailsToString on _SetPublicLinkDetails {
String _$toString() { String _$toString() {
// ignore: unnecessary_string_interpolations // ignore: unnecessary_string_interpolations

View file

@ -12,6 +12,7 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
on<_SetResult>(_onSetResult); on<_SetResult>(_onSetResult);
on<_SetPublicLinkDetails>(_onSetPublicLinkDetails); on<_SetPublicLinkDetails>(_onSetPublicLinkDetails);
on<_SetPasswordLinkDetails>(_onSetPasswordLinkDetails); on<_SetPasswordLinkDetails>(_onSetPasswordLinkDetails);
on<_CancelFileDownload>(_onCancelFileDownload);
on<_SetError>(_onSetError); on<_SetError>(_onSetError);
} }
@ -19,6 +20,20 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
@override @override
String get tag => _log.fullName; String get tag => _log.fullName;
@override
bool Function(dynamic, dynamic)? get shouldLog => (currentState, nextState) {
currentState = currentState as _State;
nextState = nextState as _State;
if (identical(currentState.fileState, nextState.fileState)) {
return true;
}
// don't log download progress
if (currentState.fileState?.progress != nextState.fileState?.progress) {
return false;
}
return true;
};
@override @override
void onError(Object error, StackTrace stackTrace) { void onError(Object error, StackTrace stackTrace) {
// we need this to prevent onError being triggered recursively // we need this to prevent onError being triggered recursively
@ -33,7 +48,7 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
} }
Future<void> _onSetMethod(_SetMethod ev, Emitter<_State> emit) async { Future<void> _onSetMethod(_SetMethod ev, Emitter<_State> emit) async {
_log.info("$ev"); _log.info(ev);
emit(state.copyWith(method: ev.method)); emit(state.copyWith(method: ev.method));
switch (ev.method) { switch (ev.method) {
case ShareMethod.file: case ShareMethod.file:
@ -48,22 +63,30 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
} }
void _onSetResult(_SetResult ev, Emitter<_State> emit) { void _onSetResult(_SetResult ev, Emitter<_State> emit) {
_log.info("$ev"); _log.info(ev);
emit(state.copyWith(result: ev.result)); emit(state.copyWith(result: ev.result));
} }
Future<void> _onSetPublicLinkDetails( Future<void> _onSetPublicLinkDetails(
_SetPublicLinkDetails ev, Emitter<_State> emit) { _SetPublicLinkDetails ev, Emitter<_State> emit) {
_log.info("$ev"); _log.info(ev);
return _doShareLink(emit, albumName: ev.albumName, password: null); return _doShareLink(emit, albumName: ev.albumName, password: null);
} }
Future<void> _onSetPasswordLinkDetails( Future<void> _onSetPasswordLinkDetails(
_SetPasswordLinkDetails ev, Emitter<_State> emit) { _SetPasswordLinkDetails ev, Emitter<_State> emit) {
_log.info("$ev"); _log.info(ev);
return _doShareLink(emit, albumName: ev.albumName, password: ev.password); return _doShareLink(emit, albumName: ev.albumName, password: ev.password);
} }
void _onCancelFileDownload(_CancelFileDownload ev, Emitter<_State> emit) {
_log.info(ev);
state.fileState?.download?.cancel();
emit(state.copyWith(
fileState: state.fileState?.copyWith(shouldRun: false),
));
}
void _onSetError(_SetError ev, Emitter<_State> emit) { void _onSetError(_SetError ev, Emitter<_State> emit) {
_log.info(ev); _log.info(ev);
emit(state.copyWith(error: ExceptionEvent(ev.error, ev.stackTrace))); emit(state.copyWith(error: ExceptionEvent(ev.error, ev.stackTrace)));
@ -72,22 +95,45 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
Future<void> _doShareFile(Emitter<_State> emit) async { Future<void> _doShareFile(Emitter<_State> emit) async {
assert(getRawPlatform() == NpPlatform.android); assert(getRawPlatform() == NpPlatform.android);
emit(state.copyWith( emit(state.copyWith(
previewState: _PreviewState(index: 0, count: files.length), fileState: _FileState.init(count: files.length),
)); ));
final results = <Tuple2<FileDescriptor, dynamic>>[]; final results = <Tuple2<FileDescriptor, dynamic>>[];
for (final pair in files.withIndex()) { for (final pair in files.withIndex()) {
final i = pair.item1, f = pair.item2; final i = pair.item1, f = pair.item2;
emit(state.copyWith( emit(state.copyWith(
previewState: state.previewState?.copyWith(index: i), fileState: state.fileState?.copyWith(
index: i,
progress: null,
),
)); ));
try { try {
final uri = await DownloadFile()(account, f, shouldNotify: false); final download = DownloadFile().build(
results.add(Tuple2(f, uri)); account,
f,
shouldNotify: false,
onProgress: (progress) {
emit(state.copyWith(
fileState: state.fileState?.copyWith(progress: progress),
));
},
);
emit(state.copyWith(
fileState: state.fileState?.copyWith(download: download),
));
final result = await download();
if (state.fileState?.shouldRun == false) {
throw const JobCanceledException();
}
results.add(Tuple2(f, result));
} on PermissionException catch (e, stackTrace) { } on PermissionException catch (e, stackTrace) {
_log.warning("[_doShareFile] Permission not granted"); _log.warning("[_doShareFile] Permission not granted");
emit(state.copyWith(error: ExceptionEvent(e, stackTrace))); emit(state.copyWith(error: ExceptionEvent(e, stackTrace)));
emit(state.copyWith(result: false)); emit(state.copyWith(result: false));
return; return;
} on JobCanceledException catch (_) {
_log.info("[_doShareFile] Job canceled");
emit(state.copyWith(result: false));
return;
} catch (e, stackTrace) { } catch (e, stackTrace) {
_log.shout("[_doShareFile] Failed while DownloadFile", e, stackTrace); _log.shout("[_doShareFile] Failed while DownloadFile", e, stackTrace);
emit(state.copyWith(error: ExceptionEvent(e, stackTrace))); emit(state.copyWith(error: ExceptionEvent(e, stackTrace)));

View file

@ -51,14 +51,31 @@ class _PreviewState {
class _FileState { class _FileState {
const _FileState({ const _FileState({
required this.index, required this.index,
required this.progress,
required this.count, required this.count,
required this.download,
required this.shouldRun,
}); });
factory _FileState.init({
required int count,
}) =>
_FileState(
index: 0,
progress: null,
count: count,
download: null,
shouldRun: true,
);
@override @override
String toString() => _$toString(); String toString() => _$toString();
final int index; final int index;
final double? progress;
final int count; final int count;
final Download? download;
final bool shouldRun;
} }
@toString @toString
@ -108,6 +125,14 @@ class _SetResult implements _Event {
final bool result; final bool result;
} }
@toString
class _CancelFileDownload implements _Event {
const _CancelFileDownload();
@override
String toString() => _$toString();
}
/// Set the details needed to share files as public link /// Set the details needed to share files as public link
@toString @toString
class _SetPublicLinkDetails implements _Event { class _SetPublicLinkDetails implements _Event {