nc-photos/app/lib/share_handler.dart

275 lines
9 KiB
Dart
Raw Normal View History

2021-09-19 23:18:20 +02:00
import 'dart:async';
2021-10-08 20:39:53 +02:00
import 'dart:math';
2021-09-19 23:18:20 +02:00
2023-03-13 12:55:39 +01:00
import 'package:clock/clock.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
2022-05-07 12:27:26 +02:00
import 'package:kiwi/kiwi.dart';
import 'package:logging/logging.dart';
import 'package:nc_photos/account.dart';
2021-07-25 07:00:38 +02:00
import 'package:nc_photos/app_localizations.dart';
2022-05-07 12:27:26 +02:00
import 'package:nc_photos/debug_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;
2022-05-07 12:27:26 +02:00
import 'package:nc_photos/entity/local_file.dart';
import 'package:nc_photos/entity/share.dart';
import 'package:nc_photos/entity/share/data_source.dart';
import 'package:nc_photos/exception_util.dart' as exception_util;
import 'package:nc_photos/internal_download_handler.dart';
import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/mobile/share.dart';
2021-10-08 20:39:53 +02:00
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
import 'package:nc_photos/snack_bar_manager.dart';
2021-10-08 20:39:53 +02:00
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/inflate_file_descriptor.dart';
2022-05-07 12:27:26 +02:00
import 'package:nc_photos/use_case/share_local.dart';
2021-10-08 20:39:53 +02:00
import 'package:nc_photos/widget/share_link_multiple_files_dialog.dart';
import 'package:nc_photos/widget/share_method_dialog.dart';
import 'package:nc_photos/widget/simple_input_dialog.dart';
2022-12-16 16:01:04 +01:00
import 'package:np_codegen/np_codegen.dart';
2023-08-27 12:58:05 +02:00
import 'package:np_platform_util/np_platform_util.dart';
2022-12-16 16:01:04 +01:00
part 'share_handler.g.dart';
/// Handle sharing to other apps
2022-12-16 16:01:04 +01:00
@npLog
class ShareHandler {
ShareHandler(
this._c, {
2021-10-08 20:39:53 +02:00
required this.context,
this.clearSelection,
}) : assert(require(_c)),
assert(InflateFileDescriptor.require(_c));
static bool require(DiContainer c) => true;
2021-10-08 20:39:53 +02:00
2022-05-07 12:27:26 +02:00
Future<void> shareLocalFiles(List<LocalFile> files) async {
if (!isSelectionCleared) {
clearSelection?.call();
}
final c = KiwiContainer().resolve<DiContainer>();
var hasShownError = false;
await ShareLocal(c)(
files,
onFailure: (f, e, stackTrace) {
if (e != null) {
_log.shout(
"[shareLocalFiles] Failed while sharing file: ${logFilename(f.logTag)}",
e,
stackTrace);
if (!hasShownError) {
SnackBarManager().showSnackBar(SnackBar(
content: Text(exception_util.toUserString(e)),
duration: k.snackBarDurationNormal,
));
hasShownError = true;
}
}
},
);
}
Future<void> shareFiles(Account account, List<FileDescriptor> fds) async {
final files = await InflateFileDescriptor(_c)(account, fds);
2021-10-08 20:39:53 +02:00
try {
final method = await _askShareMethod(files);
2021-10-08 20:39:53 +02:00
if (method == null) {
// user canceled
return;
} else if (method == ShareMethod.publicLink) {
return await _shareAsLink(account, files, false);
} else if (method == ShareMethod.passwordLink) {
return await _shareAsLink(account, files, true);
} else if (method == ShareMethod.preview) {
return await _shareAsPreview(account, files);
2021-10-08 20:39:53 +02:00
} else {
return await _shareAsFile(account, files);
}
} catch (e, stackTrace) {
_log.shout("[shareFiles] Failed while sharing files", e, stackTrace);
SnackBarManager().showSnackBar(SnackBar(
content: Text(exception_util.toUserString(e)),
duration: k.snackBarDurationNormal,
));
} finally {
if (!isSelectionCleared) {
clearSelection?.call();
}
}
}
Future<ShareMethod?> _askShareMethod(List<File> files) {
return showDialog<ShareMethod>(
context: context,
builder: (context) => ShareMethodDialog(
isSupportPerview: files.any((f) => file_util.isSupportedImageFormat(f)),
),
);
}
Future<void> _shareAsPreview(Account account, List<File> files) async {
2023-08-27 12:58:05 +02:00
assert(getRawPlatform() == NpPlatform.android);
final results =
await InternalDownloadHandler(account).downloadPreviews(context, files);
final share = AndroidFileShare(results.entries
.map((e) => AndroidFileShareFile(e.value as String, e.key.contentType))
.toList());
return share.share();
}
2021-10-08 20:39:53 +02:00
Future<void> _shareAsFile(Account account, List<File> files) async {
2023-08-27 12:58:05 +02:00
assert(getRawPlatform() == NpPlatform.android);
final results =
await InternalDownloadHandler(account).downloadFiles(context, files);
2023-09-03 11:12:57 +02:00
if (results.isEmpty) {
return;
}
final share = AndroidFileShare(results.entries
.map((e) => AndroidFileShareFile(e.value as String, e.key.contentType))
.toList());
2022-07-28 18:59:26 +02:00
return share.share();
}
2021-10-08 20:39:53 +02:00
Future<void> _shareAsLink(
Account account, List<File> files, bool isPasswordProtected) async {
if (files.length == 1) {
String? password;
if (isPasswordProtected) {
password = await _askPassword();
if (password == null) {
// user canceled
return;
}
}
return _shareFileAsLink(account, files.first, password);
} else {
final result = await _askDirDetail(context, isPasswordProtected);
if (result == null) {
// user canceled
return;
}
_log.info("[_shareAsLink] Share as folder: ${result.albumName}");
2022-07-25 07:04:22 +02:00
SnackBarManager().showSnackBar(
SnackBar(
content: Text(L10n.global().createShareProgressText),
duration: k.snackBarDurationShort,
),
canBeReplaced: true,
);
2021-10-08 20:39:53 +02:00
clearSelection?.call();
isSelectionCleared = true;
final c = KiwiContainer().resolve<DiContainer>();
final path = await _createDir(c.fileRepo, account, result.albumName);
await _copyFilesToDir(c.fileRepo, account, files, path);
2021-10-08 20:39:53 +02:00
return _shareFileAsLink(
account,
File(
path: path,
isCollection: true,
),
result.password,
);
}
}
2021-10-08 20:39:53 +02:00
Future<void> _shareFileAsLink(
Account account, File file, String? password) async {
final shareRepo = ShareRepo(ShareRemoteDataSource());
try {
final share = await CreateLinkShare(shareRepo)(
account,
file,
password: password,
);
2024-05-20 16:15:30 +02:00
await Clipboard.setData(ClipboardData(text: share.url!));
SnackBarManager().showSnackBar(SnackBar(
content: Text(L10n.global().linkCopiedNotification),
duration: k.snackBarDurationNormal,
));
2023-08-27 12:58:05 +02:00
if (getRawPlatform() == NpPlatform.android) {
final textShare = AndroidTextShare(share.url!);
2022-07-28 18:59:26 +02:00
await textShare.share();
}
} catch (e, stackTrace) {
_log.shout(
2021-10-08 20:39:53 +02:00
"[_shareFileAsLink] Failed while CreateLinkShare", e, stackTrace);
SnackBarManager().showSnackBar(SnackBar(
content: Text(exception_util.toUserString(e)),
duration: k.snackBarDurationNormal,
));
}
}
2021-10-08 20:39:53 +02:00
Future<String?> _askPassword() {
return 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,
),
);
}
2021-10-08 20:39:53 +02:00
Future<ShareLinkMultipleFilesDialogResult?> _askDirDetail(
BuildContext context, bool isPasswordProtected) {
return showDialog<ShareLinkMultipleFilesDialogResult>(
context: context,
builder: (_) => ShareLinkMultipleFilesDialog(
shouldAskPassword: isPasswordProtected,
),
);
}
Future<String> _createDir(
FileRepo fileRepo, Account account, String name) async {
// add a intermediate dir to allow shared dirs having the same name. Since
// the dir names are public, we can't add random pre/suffix
2023-03-13 12:55:39 +01:00
final timestamp = clock.now().millisecondsSinceEpoch;
2021-10-08 20:39:53 +02:00
final random = Random().nextInt(0xFFFFFF);
final dirName =
"${timestamp.toRadixString(16)}-${random.toRadixString(16).padLeft(6, "0")}";
final path =
"${remote_storage_util.getRemoteLinkSharesDir(account)}/$dirName/$name";
await CreateDir(fileRepo)(account, path);
return path;
}
Future<void> _copyFilesToDir(FileRepo fileRepo, Account account,
List<File> files, String dirPath) async {
var failureCount = 0;
for (final f in files) {
try {
2021-10-17 11:48:00 +02:00
await Copy(fileRepo)(account, f, "$dirPath/${f.filename}");
2021-10-08 20:39:53 +02:00
} catch (e, stackTrace) {
_log.severe(
"[_copyFilesToDir] Failed while copying file: $f", e, stackTrace);
++failureCount;
}
}
if (failureCount != 0) {
SnackBarManager().showSnackBar(SnackBar(
content: Text(L10n.global().copyItemsFailureNotification(failureCount)),
duration: k.snackBarDurationNormal,
));
}
}
final DiContainer _c;
2021-10-08 20:39:53 +02:00
final BuildContext context;
final VoidCallback? clearSelection;
var isSelectionCleared = false;
}