mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-01-22 16:56:19 +01:00
382 lines
12 KiB
Dart
382 lines
12 KiB
Dart
import 'dart:async';
|
|
import 'dart:math';
|
|
|
|
import 'package:clock/clock.dart';
|
|
import 'package:copy_with/copy_with.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
import 'package:kiwi/kiwi.dart';
|
|
import 'package:logging/logging.dart';
|
|
import 'package:nc_photos/account.dart';
|
|
import 'package:nc_photos/app_localizations.dart';
|
|
import 'package:nc_photos/bloc_util.dart';
|
|
import 'package:nc_photos/di_container.dart';
|
|
import 'package:nc_photos/entity/file.dart';
|
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
|
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_util.dart' as exception_util;
|
|
import 'package:nc_photos/k.dart' as k;
|
|
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/toast.dart';
|
|
import 'package:nc_photos/use_case/copy.dart';
|
|
import 'package:nc_photos/use_case/create_dir.dart';
|
|
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_preview.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/share_link_multiple_files_dialog.dart';
|
|
import 'package:nc_photos/widget/simple_input_dialog.dart';
|
|
import 'package:nc_photos_plugin/nc_photos_plugin.dart';
|
|
import 'package:np_codegen/np_codegen.dart';
|
|
import 'package:np_collection/np_collection.dart';
|
|
import 'package:np_platform_util/np_platform_util.dart';
|
|
import 'package:to_string/to_string.dart';
|
|
|
|
part 'file_sharer_dialog.g.dart';
|
|
part 'file_sharer_dialog/bloc.dart';
|
|
part 'file_sharer_dialog/state_event.dart';
|
|
part 'file_sharer_dialog/type.dart';
|
|
|
|
/// Dialog to let user share files with different options
|
|
///
|
|
/// Return true if the files are actually shared, false if user cancelled or
|
|
/// some errors occurred (e.g., missing permission)
|
|
class FileSharerDialog extends StatelessWidget {
|
|
const FileSharerDialog({
|
|
super.key,
|
|
required this.account,
|
|
required this.files,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return BlocProvider(
|
|
create: (context) => _Bloc(
|
|
container: KiwiContainer().resolve(),
|
|
account: account,
|
|
files: files,
|
|
),
|
|
child: const _WrappedFileSharerDialog(),
|
|
);
|
|
}
|
|
|
|
final Account account;
|
|
final List<FileDescriptor> files;
|
|
}
|
|
|
|
class _WrappedFileSharerDialog extends StatelessWidget {
|
|
const _WrappedFileSharerDialog();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MultiBlocListener(
|
|
listeners: [
|
|
_BlocListener(
|
|
listenWhen: (previous, current) => previous.error != current.error,
|
|
listener: (context, state) {
|
|
if (state.error != null) {
|
|
if (state.error!.error is PermissionException) {
|
|
AppToast.showToast(
|
|
context,
|
|
msg: L10n.global().errorNoStoragePermission,
|
|
duration: k.snackBarDurationNormal,
|
|
);
|
|
} else {
|
|
AppToast.showToast(
|
|
context,
|
|
msg: exception_util.toUserString(state.error!.error),
|
|
duration: k.snackBarDurationNormal,
|
|
);
|
|
}
|
|
}
|
|
},
|
|
),
|
|
_BlocListener(
|
|
listenWhen: (previous, current) =>
|
|
previous.message != current.message,
|
|
listener: (context, state) {
|
|
if (state.message != null) {
|
|
AppToast.showToast(
|
|
context,
|
|
msg: state.message!,
|
|
duration: k.snackBarDurationNormal,
|
|
);
|
|
}
|
|
},
|
|
),
|
|
_BlocListener(
|
|
listenWhen: (previous, current) => previous.result != current.result,
|
|
listener: (context, state) {
|
|
if (state.result != null) {
|
|
Navigator.of(context).pop(state.result);
|
|
}
|
|
},
|
|
),
|
|
],
|
|
child: _BlocBuilder(
|
|
buildWhen: (previous, current) => previous.method != current.method,
|
|
builder: (context, state) {
|
|
switch (state.method) {
|
|
case null:
|
|
return const _ShareMethodDialog();
|
|
case ShareMethod.file:
|
|
return const _ShareFileDialog();
|
|
case ShareMethod.preview:
|
|
return const _SharePreviewDialog();
|
|
case ShareMethod.publicLink:
|
|
return const _SharePublicLinkDialog();
|
|
case ShareMethod.passwordLink:
|
|
return const _SharePasswordLinkDialog();
|
|
}
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _ShareMethodDialog extends StatelessWidget {
|
|
const _ShareMethodDialog();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final isSupportPerview =
|
|
context.bloc.files.any((f) => file_util.isSupportedImageFormat(f));
|
|
return SimpleDialog(
|
|
title: Text(L10n.global().shareMethodDialogTitle),
|
|
children: [
|
|
if (getRawPlatform() == NpPlatform.android) ...[
|
|
if (isSupportPerview)
|
|
SimpleDialogOption(
|
|
child: ListTile(
|
|
title: Text(L10n.global().shareMethodPreviewTitle),
|
|
subtitle: Text(L10n.global().shareMethodPreviewDescription),
|
|
),
|
|
onPressed: () {
|
|
context.addEvent(const _SetMethod(ShareMethod.preview));
|
|
},
|
|
),
|
|
SimpleDialogOption(
|
|
child: ListTile(
|
|
title: Text(L10n.global().shareMethodOriginalFileTitle),
|
|
subtitle: Text(L10n.global().shareMethodOriginalFileDescription),
|
|
),
|
|
onPressed: () {
|
|
context.addEvent(const _SetMethod(ShareMethod.file));
|
|
},
|
|
),
|
|
],
|
|
SimpleDialogOption(
|
|
child: ListTile(
|
|
title: Text(L10n.global().shareMethodPublicLinkTitle),
|
|
subtitle: Text(L10n.global().shareMethodPublicLinkDescription),
|
|
),
|
|
onPressed: () {
|
|
context.addEvent(const _SetMethod(ShareMethod.publicLink));
|
|
},
|
|
),
|
|
SimpleDialogOption(
|
|
child: ListTile(
|
|
title: Text(L10n.global().shareMethodPasswordLinkTitle),
|
|
subtitle: Text(L10n.global().shareMethodPasswordLinkDescription),
|
|
),
|
|
onPressed: () {
|
|
context.addEvent(const _SetMethod(ShareMethod.passwordLink));
|
|
},
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class _ShareFileDialog extends StatelessWidget {
|
|
const _ShareFileDialog();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return _BlocSelector<_FileState?>(
|
|
selector: (state) => state.fileState,
|
|
builder: (context, fileState) {
|
|
if (fileState != null) {
|
|
return DownloadProgressDialog(
|
|
max: fileState.count,
|
|
current: fileState.index,
|
|
progress: fileState.progress,
|
|
label: context.bloc.files[fileState.index].filename,
|
|
onCancel: () {
|
|
context.addEvent(const _CancelFileDownload());
|
|
},
|
|
);
|
|
} else {
|
|
return ProcessingDialog(
|
|
text: L10n.global().genericProcessingDialogContent,
|
|
);
|
|
}
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
class _SharePreviewDialog extends StatelessWidget {
|
|
const _SharePreviewDialog();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return _BlocBuilder(
|
|
buildWhen: (previous, current) =>
|
|
previous.previewState?.index != current.previewState?.index ||
|
|
previous.previewState?.count != current.previewState?.count,
|
|
builder: (context, state) {
|
|
final text = state.previewState?.index != null &&
|
|
state.previewState?.count != null
|
|
? " (${state.previewState!.index}/${state.previewState!.count})"
|
|
: "";
|
|
return ProcessingDialog(
|
|
text: L10n.global().shareDownloadingDialogContent + text,
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
class _SharePublicLinkDialog extends StatefulWidget {
|
|
const _SharePublicLinkDialog();
|
|
|
|
@override
|
|
State<StatefulWidget> createState() => _SharePublicLinkDialogState();
|
|
}
|
|
|
|
class _SharePublicLinkDialogState extends State<_SharePublicLinkDialog> {
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
WidgetsBinding.instance.addPostFrameCallback((_) => _askDetails());
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ProcessingDialog(text: L10n.global().createShareProgressText);
|
|
}
|
|
|
|
Future<void> _askDetails() async {
|
|
if (_bloc.files.length == 1) {
|
|
_bloc.add(const _SetPublicLinkDetails());
|
|
} else {
|
|
final result = await showDialog<ShareLinkMultipleFilesDialogResult>(
|
|
context: context,
|
|
builder: (context) => const ShareLinkMultipleFilesDialog(
|
|
shouldAskPassword: false,
|
|
),
|
|
);
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
if (result == null) {
|
|
_bloc.add(const _SetResult(false));
|
|
return;
|
|
} else {
|
|
_bloc.add(_SetPublicLinkDetails(
|
|
albumName: result.albumName,
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
late final _bloc = context.bloc;
|
|
}
|
|
|
|
class _SharePasswordLinkDialog extends StatefulWidget {
|
|
const _SharePasswordLinkDialog();
|
|
|
|
@override
|
|
State<StatefulWidget> createState() => _SharePasswordLinkDialogState();
|
|
}
|
|
|
|
class _SharePasswordLinkDialogState extends State<_SharePasswordLinkDialog> {
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
WidgetsBinding.instance.addPostFrameCallback((_) => _askDetails());
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return _BlocBuilder(
|
|
buildWhen: (previous, current) =>
|
|
previous.passwordLinkState?.password !=
|
|
current.passwordLinkState?.password,
|
|
builder: (context, state) {
|
|
if (state.passwordLinkState?.password == null) {
|
|
return Container();
|
|
} else {
|
|
return ProcessingDialog(text: L10n.global().createShareProgressText);
|
|
}
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> _askDetails() async {
|
|
if (_bloc.files.length == 1) {
|
|
final result = await showDialog<String>(
|
|
context: context,
|
|
builder: (context) => SimpleInputDialog(
|
|
hintText: L10n.global().passwordInputHint,
|
|
buttonText: MaterialLocalizations.of(context).okButtonLabel,
|
|
validator: (value) {
|
|
if (value?.isNotEmpty != true) {
|
|
return L10n.global().passwordInputInvalidEmpty;
|
|
}
|
|
return null;
|
|
},
|
|
obscureText: true,
|
|
),
|
|
);
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
if (result == null) {
|
|
_bloc.add(const _SetResult(false));
|
|
return;
|
|
} else {
|
|
_bloc.add(_SetPasswordLinkDetails(password: result));
|
|
}
|
|
} else {
|
|
final result = await showDialog<ShareLinkMultipleFilesDialogResult>(
|
|
context: context,
|
|
builder: (context) => const ShareLinkMultipleFilesDialog(
|
|
shouldAskPassword: true,
|
|
),
|
|
);
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
if (result == null) {
|
|
_bloc.add(const _SetResult(false));
|
|
return;
|
|
} else {
|
|
_bloc.add(_SetPasswordLinkDetails(
|
|
albumName: result.albumName,
|
|
password: result.password!,
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|