mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-03-04 14:28:56 +01:00
Support ATTACH_DATA intent on android
This is typically used to set wallpaper or contact photos
This commit is contained in:
parent
a142436e7b
commit
d5de52a789
15 changed files with 407 additions and 129 deletions
|
@ -19,24 +19,35 @@ class ShareChannelHandler(activity: Activity) :
|
|||
try {
|
||||
shareItems(
|
||||
call.argument("fileUris")!!,
|
||||
call.argument("mimeTypes")!!,
|
||||
result
|
||||
call.argument("mimeTypes")!!, result
|
||||
)
|
||||
} catch (e: Throwable) {
|
||||
result.error("systemException", e.toString(), null)
|
||||
}
|
||||
}
|
||||
|
||||
"shareText" -> {
|
||||
try {
|
||||
shareText(
|
||||
call.argument("text")!!,
|
||||
call.argument("mimeType"),
|
||||
call.argument("text")!!, call.argument("mimeType"),
|
||||
result
|
||||
)
|
||||
} catch (e: Throwable) {
|
||||
result.error("systemException", e.toString(), null)
|
||||
}
|
||||
}
|
||||
|
||||
"shareAsAttachData" -> {
|
||||
try {
|
||||
shareAsAttachData(
|
||||
call.argument("fileUri")!!, call.argument("mimeType"),
|
||||
result
|
||||
)
|
||||
} catch (e: Throwable) {
|
||||
result.error("systemException", e.toString(), null)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
result.notImplemented()
|
||||
}
|
||||
|
@ -44,8 +55,7 @@ class ShareChannelHandler(activity: Activity) :
|
|||
}
|
||||
|
||||
private fun shareItems(
|
||||
fileUris: List<String>,
|
||||
mimeTypes: List<String?>,
|
||||
fileUris: List<String>, mimeTypes: List<String?>,
|
||||
result: MethodChannel.Result
|
||||
) {
|
||||
assert(fileUris.isNotEmpty())
|
||||
|
@ -104,6 +114,26 @@ class ShareChannelHandler(activity: Activity) :
|
|||
result.success(null)
|
||||
}
|
||||
|
||||
private fun shareAsAttachData(
|
||||
fileUri: String, mimeType: String?, result: MethodChannel.Result
|
||||
) {
|
||||
val intent = Intent().apply {
|
||||
action = Intent.ACTION_ATTACH_DATA
|
||||
if (mimeType == null) {
|
||||
data = Uri.parse(fileUri)
|
||||
} else {
|
||||
setDataAndType(Uri.parse(fileUri), mimeType)
|
||||
}
|
||||
addCategory(Intent.CATEGORY_DEFAULT)
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
val chooser = Intent.createChooser(
|
||||
intent, _context.getString(R.string.attach_data_chooser_title)
|
||||
)
|
||||
_context.startActivity(chooser)
|
||||
result.success(null)
|
||||
}
|
||||
|
||||
private val _activity = activity
|
||||
private val _context get() = _activity
|
||||
}
|
||||
|
|
|
@ -53,8 +53,8 @@ class LocalFileMediaStoreDataSource implements LocalFileDataSource {
|
|||
onFailure?.call(f, ArgumentError("File not supported"), null);
|
||||
});
|
||||
|
||||
final share = AndroidFileShare(uriFiles.map((e) => e.uri).toList(),
|
||||
uriFiles.map((e) => e.mime).toList());
|
||||
final share = AndroidFileShare(
|
||||
uriFiles.map((e) => AndroidFileShareFile(e.uri, e.mime)).toList());
|
||||
try {
|
||||
await share.share();
|
||||
} catch (e, stackTrace) {
|
||||
|
|
124
app/lib/internal_download_handler.dart
Normal file
124
app/lib/internal_download_handler.dart
Normal file
|
@ -0,0 +1,124 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:nc_photos/account.dart';
|
||||
import 'package:nc_photos/app_localizations.dart';
|
||||
import 'package:nc_photos/entity/file.dart';
|
||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||
import 'package:nc_photos/iterable_extension.dart';
|
||||
import 'package:nc_photos/k.dart' as k;
|
||||
import 'package:nc_photos/map_extension.dart';
|
||||
import 'package:nc_photos/snack_bar_manager.dart';
|
||||
import 'package:nc_photos/use_case/download_file.dart';
|
||||
import 'package:nc_photos/use_case/download_preview.dart';
|
||||
import 'package:nc_photos/widget/processing_dialog.dart';
|
||||
import 'package:nc_photos_plugin/nc_photos_plugin.dart';
|
||||
import 'package:np_codegen/np_codegen.dart';
|
||||
|
||||
part 'internal_download_handler.g.dart';
|
||||
|
||||
/// Download file to internal dir
|
||||
@npLog
|
||||
class InternalDownloadHandler {
|
||||
const InternalDownloadHandler(this.account);
|
||||
|
||||
Future<Map<File, dynamic>> downloadPreviews(
|
||||
BuildContext context, List<File> files) async {
|
||||
final controller = StreamController<String>();
|
||||
unawaited(
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => StreamBuilder(
|
||||
stream: controller.stream,
|
||||
builder: (context, snapshot) => ProcessingDialog(
|
||||
text: L10n.global().shareDownloadingDialogContent +
|
||||
(snapshot.hasData ? " ${snapshot.data}" : ""),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
try {
|
||||
final results = <MapEntry<File, dynamic>>[];
|
||||
for (final pair in files.withIndex()) {
|
||||
final i = pair.item1, f = pair.item2;
|
||||
controller.add("($i/${files.length})");
|
||||
try {
|
||||
final dynamic uri;
|
||||
if (file_util.isSupportedImageFormat(f) &&
|
||||
f.contentType != "image/gif") {
|
||||
uri = await DownloadPreview()(account, f);
|
||||
} else {
|
||||
uri = await DownloadFile()(account, f);
|
||||
}
|
||||
results.add(MapEntry(f, uri));
|
||||
} catch (e, stacktrace) {
|
||||
_log.shout(
|
||||
"[downloadPreviews] Failed while DownloadPreview", e, stacktrace);
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(exception_util.toUserString(e)),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
}
|
||||
}
|
||||
return results.toMap();
|
||||
} finally {
|
||||
// dismiss the dialog
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<File, dynamic>> downloadFiles(
|
||||
BuildContext context, List<File> files) async {
|
||||
final controller = StreamController<String>();
|
||||
unawaited(
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => StreamBuilder(
|
||||
stream: controller.stream,
|
||||
builder: (context, snapshot) => ProcessingDialog(
|
||||
text: L10n.global().shareDownloadingDialogContent +
|
||||
(snapshot.hasData ? " ${snapshot.data}" : ""),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
try {
|
||||
final results = <MapEntry<File, dynamic>>[];
|
||||
for (final pair in files.withIndex()) {
|
||||
final i = pair.item1, f = pair.item2;
|
||||
controller.add("($i/${files.length})");
|
||||
try {
|
||||
results.add(MapEntry(
|
||||
f,
|
||||
await DownloadFile()(
|
||||
account,
|
||||
f,
|
||||
shouldNotify: false,
|
||||
)));
|
||||
} on PermissionException catch (_) {
|
||||
_log.warning("[downloadFiles] Permission not granted");
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(L10n.global().errorNoStoragePermission),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
rethrow;
|
||||
} catch (e, stacktrace) {
|
||||
_log.shout(
|
||||
"[downloadFiles] Failed while downloadFile", e, stacktrace);
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(exception_util.toUserString(e)),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
}
|
||||
}
|
||||
return results.toMap();
|
||||
} finally {
|
||||
// dismiss the dialog
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
|
||||
final Account account;
|
||||
}
|
15
app/lib/internal_download_handler.g.dart
Normal file
15
app/lib/internal_download_handler.g.dart
Normal file
|
@ -0,0 +1,15 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'internal_download_handler.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// NpLogGenerator
|
||||
// **************************************************************************
|
||||
|
||||
extension _$InternalDownloadHandlerNpLog on InternalDownloadHandler {
|
||||
// ignore: unused_element
|
||||
Logger get _log => log;
|
||||
|
||||
static final log =
|
||||
Logger("internal_download_handler.InternalDownloadHandler");
|
||||
}
|
|
@ -1419,6 +1419,10 @@
|
|||
"removeCollectionsFailedNotification": "Failed to remove some collections",
|
||||
"accountSettingsTooltip": "Account settings",
|
||||
"contributorsTooltip": "Contributors",
|
||||
"setAsTooltip": "Set as",
|
||||
"@setAsTooltip": {
|
||||
"description": "e.g., set as wallpaper"
|
||||
},
|
||||
|
||||
"errorUnauthenticated": "Unauthenticated access. Please sign-in again if the problem continues",
|
||||
"@errorUnauthenticated": {
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
"createCollectionDialogNextcloudAlbumDescription",
|
||||
"removeCollectionsFailedNotification",
|
||||
"accountSettingsTooltip",
|
||||
"contributorsTooltip"
|
||||
"contributorsTooltip",
|
||||
"setAsTooltip"
|
||||
],
|
||||
|
||||
"de": [
|
||||
|
@ -106,6 +107,7 @@
|
|||
"removeCollectionsFailedNotification",
|
||||
"accountSettingsTooltip",
|
||||
"contributorsTooltip",
|
||||
"setAsTooltip",
|
||||
"errorAlbumDowngrade"
|
||||
],
|
||||
|
||||
|
@ -216,7 +218,8 @@
|
|||
"createCollectionDialogNextcloudAlbumDescription",
|
||||
"removeCollectionsFailedNotification",
|
||||
"accountSettingsTooltip",
|
||||
"contributorsTooltip"
|
||||
"contributorsTooltip",
|
||||
"setAsTooltip"
|
||||
],
|
||||
|
||||
"es": [
|
||||
|
@ -226,7 +229,8 @@
|
|||
"settingsSeedColorPickerSystemColorButtonLabel",
|
||||
"searchLandingPeopleListEmptyText2",
|
||||
"accountSettingsTooltip",
|
||||
"contributorsTooltip"
|
||||
"contributorsTooltip",
|
||||
"setAsTooltip"
|
||||
],
|
||||
|
||||
"fi": [
|
||||
|
@ -236,7 +240,8 @@
|
|||
"settingsSeedColorPickerSystemColorButtonLabel",
|
||||
"searchLandingPeopleListEmptyText2",
|
||||
"accountSettingsTooltip",
|
||||
"contributorsTooltip"
|
||||
"contributorsTooltip",
|
||||
"setAsTooltip"
|
||||
],
|
||||
|
||||
"fr": [
|
||||
|
@ -366,7 +371,8 @@
|
|||
"createCollectionDialogNextcloudAlbumDescription",
|
||||
"removeCollectionsFailedNotification",
|
||||
"accountSettingsTooltip",
|
||||
"contributorsTooltip"
|
||||
"contributorsTooltip",
|
||||
"setAsTooltip"
|
||||
],
|
||||
|
||||
"it": [
|
||||
|
@ -671,6 +677,7 @@
|
|||
"removeCollectionsFailedNotification",
|
||||
"accountSettingsTooltip",
|
||||
"contributorsTooltip",
|
||||
"setAsTooltip",
|
||||
"errorUnauthenticated",
|
||||
"errorDisconnected",
|
||||
"errorLocked",
|
||||
|
@ -1021,6 +1028,7 @@
|
|||
"removeCollectionsFailedNotification",
|
||||
"accountSettingsTooltip",
|
||||
"contributorsTooltip",
|
||||
"setAsTooltip",
|
||||
"errorUnauthenticated",
|
||||
"errorDisconnected",
|
||||
"errorLocked",
|
||||
|
@ -1172,7 +1180,8 @@
|
|||
"createCollectionDialogNextcloudAlbumDescription",
|
||||
"removeCollectionsFailedNotification",
|
||||
"accountSettingsTooltip",
|
||||
"contributorsTooltip"
|
||||
"contributorsTooltip",
|
||||
"setAsTooltip"
|
||||
],
|
||||
|
||||
"pt": [
|
||||
|
@ -1193,7 +1202,8 @@
|
|||
"createCollectionDialogNextcloudAlbumDescription",
|
||||
"removeCollectionsFailedNotification",
|
||||
"accountSettingsTooltip",
|
||||
"contributorsTooltip"
|
||||
"contributorsTooltip",
|
||||
"setAsTooltip"
|
||||
],
|
||||
|
||||
"ru": [
|
||||
|
@ -1319,7 +1329,8 @@
|
|||
"createCollectionDialogNextcloudAlbumDescription",
|
||||
"removeCollectionsFailedNotification",
|
||||
"accountSettingsTooltip",
|
||||
"contributorsTooltip"
|
||||
"contributorsTooltip",
|
||||
"setAsTooltip"
|
||||
],
|
||||
|
||||
"zh": [
|
||||
|
@ -1445,7 +1456,8 @@
|
|||
"createCollectionDialogNextcloudAlbumDescription",
|
||||
"removeCollectionsFailedNotification",
|
||||
"accountSettingsTooltip",
|
||||
"contributorsTooltip"
|
||||
"contributorsTooltip",
|
||||
"setAsTooltip"
|
||||
],
|
||||
|
||||
"zh_Hant": [
|
||||
|
@ -1571,6 +1583,7 @@
|
|||
"createCollectionDialogNextcloudAlbumDescription",
|
||||
"removeCollectionsFailedNotification",
|
||||
"accountSettingsTooltip",
|
||||
"contributorsTooltip"
|
||||
"contributorsTooltip",
|
||||
"setAsTooltip"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -8,12 +8,17 @@ class Share {
|
|||
"mimeTypes": mimeTypes,
|
||||
});
|
||||
|
||||
static Future<void> shareText(
|
||||
String text, String? mimeType) =>
|
||||
static Future<void> shareText(String text, String? mimeType) =>
|
||||
_channel.invokeMethod("shareText", <String, dynamic>{
|
||||
"text": text,
|
||||
"mimeType": mimeType,
|
||||
});
|
||||
|
||||
static Future<void> shareAsAttachData(String fileUri, String? mimeType) =>
|
||||
_channel.invokeMethod("shareAsAttachData", <String, dynamic>{
|
||||
"fileUri": fileUri,
|
||||
"mimeType": mimeType,
|
||||
});
|
||||
|
||||
static const _channel = MethodChannel("com.nkming.nc_photos/share");
|
||||
}
|
||||
|
|
|
@ -1,16 +1,29 @@
|
|||
import 'package:nc_photos/mobile/android/share.dart';
|
||||
import 'package:nc_photos/platform/share.dart' as itf;
|
||||
|
||||
class AndroidFileShare extends itf.FileShare {
|
||||
AndroidFileShare(this.fileUris, this.mimeTypes);
|
||||
class AndroidFileShareFile {
|
||||
const AndroidFileShareFile(this.fileUri, this.mimeType);
|
||||
|
||||
final String fileUri;
|
||||
final String? mimeType;
|
||||
}
|
||||
|
||||
class AndroidFileShare implements itf.FileShare {
|
||||
const AndroidFileShare(this.files);
|
||||
|
||||
@override
|
||||
share() {
|
||||
return Share.shareItems(fileUris, mimeTypes);
|
||||
Future<void> share() {
|
||||
final uris = files.map((e) => e.fileUri).toList();
|
||||
final mimes = files.map((e) => e.mimeType).toList();
|
||||
return Share.shareItems(uris, mimes);
|
||||
}
|
||||
|
||||
final List<String> fileUris;
|
||||
final List<String?> mimeTypes;
|
||||
Future<void> setAs() {
|
||||
assert(files.length == 1);
|
||||
return Share.shareAsAttachData(files.first.fileUri, files.first.mimeType);
|
||||
}
|
||||
|
||||
final List<AndroidFileShareFile> files;
|
||||
}
|
||||
|
||||
class AndroidTextShare extends itf.TextShare {
|
||||
|
|
86
app/lib/set_as_handler.dart
Normal file
86
app/lib/set_as_handler.dart
Normal file
|
@ -0,0 +1,86 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:nc_photos/account.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/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';
|
||||
import 'package:nc_photos/platform/k.dart' as platform_k;
|
||||
import 'package:nc_photos/snack_bar_manager.dart';
|
||||
import 'package:nc_photos/use_case/inflate_file_descriptor.dart';
|
||||
import 'package:nc_photos/widget/set_as_method_dialog.dart';
|
||||
import 'package:np_codegen/np_codegen.dart';
|
||||
|
||||
part 'set_as_handler.g.dart';
|
||||
|
||||
/// A special way to share image to other apps
|
||||
@npLog
|
||||
class SetAsHandler {
|
||||
SetAsHandler(
|
||||
this._c, {
|
||||
required this.context,
|
||||
this.clearSelection,
|
||||
});
|
||||
|
||||
Future<void> setAsFile(Account account, FileDescriptor fd) async {
|
||||
try {
|
||||
final file = (await InflateFileDescriptor(_c)(account, [fd])).first;
|
||||
final method = await _askSetAsMethod(file);
|
||||
switch (method) {
|
||||
case SetAsMethod.preview:
|
||||
return await _setAsAsPreview(account, file);
|
||||
case SetAsMethod.file:
|
||||
return await _setAsAsFile(account, file);
|
||||
case null:
|
||||
return;
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
_log.shout("[setAsFile] Failed while sharing files", e, stackTrace);
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(exception_util.toUserString(e)),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
} finally {
|
||||
if (!isSelectionCleared) {
|
||||
clearSelection?.call();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<SetAsMethod?> _askSetAsMethod(File file) {
|
||||
return showDialog<SetAsMethod>(
|
||||
context: context,
|
||||
builder: (context) => const SetAsMethodDialog(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _setAsAsPreview(Account account, File file) async {
|
||||
assert(platform_k.isAndroid);
|
||||
final results = await InternalDownloadHandler(account)
|
||||
.downloadPreviews(context, [file]);
|
||||
final share = AndroidFileShare(results.entries
|
||||
.map((e) => AndroidFileShareFile(e.value as String, e.key.contentType))
|
||||
.toList());
|
||||
return share.setAs();
|
||||
}
|
||||
|
||||
Future<void> _setAsAsFile(Account account, File file) async {
|
||||
assert(platform_k.isAndroid);
|
||||
final results =
|
||||
await InternalDownloadHandler(account).downloadFiles(context, [file]);
|
||||
final share = AndroidFileShare(results.entries
|
||||
.map((e) => AndroidFileShareFile(e.value as String, e.key.contentType))
|
||||
.toList());
|
||||
return share.setAs();
|
||||
}
|
||||
|
||||
final DiContainer _c;
|
||||
final BuildContext context;
|
||||
final VoidCallback? clearSelection;
|
||||
var isSelectionCleared = false;
|
||||
}
|
14
app/lib/set_as_handler.g.dart
Normal file
14
app/lib/set_as_handler.g.dart
Normal file
|
@ -0,0 +1,14 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'set_as_handler.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// NpLogGenerator
|
||||
// **************************************************************************
|
||||
|
||||
extension _$SetAsHandlerNpLog on SetAsHandler {
|
||||
// ignore: unused_element
|
||||
Logger get _log => log;
|
||||
|
||||
static final log = Logger("set_as_handler.SetAsHandler");
|
||||
}
|
|
@ -17,7 +17,7 @@ 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/iterable_extension.dart';
|
||||
import 'package:nc_photos/internal_download_handler.dart';
|
||||
import 'package:nc_photos/k.dart' as k;
|
||||
import 'package:nc_photos/mobile/share.dart';
|
||||
import 'package:nc_photos/platform/k.dart' as platform_k;
|
||||
|
@ -26,17 +26,12 @@ import 'package:nc_photos/snack_bar_manager.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/use_case/share_local.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_method_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:tuple/tuple.dart';
|
||||
|
||||
part 'share_handler.g.dart';
|
||||
|
||||
|
@ -118,100 +113,21 @@ class ShareHandler {
|
|||
|
||||
Future<void> _shareAsPreview(Account account, List<File> files) async {
|
||||
assert(platform_k.isAndroid);
|
||||
final controller = StreamController<String>();
|
||||
unawaited(
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => StreamBuilder(
|
||||
stream: controller.stream,
|
||||
builder: (context, snapshot) => ProcessingDialog(
|
||||
text: L10n.global().shareDownloadingDialogContent +
|
||||
(snapshot.hasData ? " ${snapshot.data}" : ""),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
final results = <Tuple2<File, dynamic>>[];
|
||||
for (final pair in files.withIndex()) {
|
||||
final i = pair.item1, f = pair.item2;
|
||||
controller.add("($i/${files.length})");
|
||||
try {
|
||||
final dynamic uri;
|
||||
if (file_util.isSupportedImageFormat(f) &&
|
||||
f.contentType != "image/gif") {
|
||||
uri = await DownloadPreview()(account, f);
|
||||
} else {
|
||||
uri = await DownloadFile()(account, f);
|
||||
}
|
||||
results.add(Tuple2(f, uri));
|
||||
} catch (e, stacktrace) {
|
||||
_log.shout(
|
||||
"[_shareAsPreview] Failed while DownloadPreview", e, stacktrace);
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(exception_util.toUserString(e)),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
}
|
||||
}
|
||||
// dismiss the dialog
|
||||
Navigator.of(context).pop();
|
||||
|
||||
final share = AndroidFileShare(
|
||||
results.map((e) => e.item2 as String).toList(),
|
||||
results.map((e) => e.item1.contentType).toList());
|
||||
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();
|
||||
}
|
||||
|
||||
Future<void> _shareAsFile(Account account, List<File> files) async {
|
||||
assert(platform_k.isAndroid);
|
||||
final controller = StreamController<String>();
|
||||
unawaited(
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => StreamBuilder(
|
||||
stream: controller.stream,
|
||||
builder: (context, snapshot) => ProcessingDialog(
|
||||
text: L10n.global().shareDownloadingDialogContent +
|
||||
(snapshot.hasData ? " ${snapshot.data}" : ""),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
final results = <Tuple2<File, dynamic>>[];
|
||||
for (final pair in files.withIndex()) {
|
||||
final i = pair.item1, f = pair.item2;
|
||||
controller.add("($i/${files.length})");
|
||||
try {
|
||||
results.add(Tuple2(
|
||||
f,
|
||||
await DownloadFile()(
|
||||
account,
|
||||
f,
|
||||
shouldNotify: false,
|
||||
)));
|
||||
} on PermissionException catch (_) {
|
||||
_log.warning("[_shareAsFile] Permission not granted");
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(L10n.global().errorNoStoragePermission),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
// dismiss the dialog
|
||||
Navigator.of(context).pop();
|
||||
rethrow;
|
||||
} catch (e, stacktrace) {
|
||||
_log.shout("[_shareAsFile] Failed while downloadFile", e, stacktrace);
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(exception_util.toUserString(e)),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
}
|
||||
}
|
||||
// dismiss the dialog
|
||||
Navigator.of(context).pop();
|
||||
|
||||
final share = AndroidFileShare(
|
||||
results.map((e) => e.item2 as String).toList(),
|
||||
results.map((e) => e.item1.contentType).toList());
|
||||
final results =
|
||||
await InternalDownloadHandler(account).downloadFiles(context, files);
|
||||
final share = AndroidFileShare(results.entries
|
||||
.map((e) => AndroidFileShareFile(e.value as String, e.key.contentType))
|
||||
.toList());
|
||||
return share.share();
|
||||
}
|
||||
|
||||
|
|
|
@ -94,10 +94,9 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
|||
}
|
||||
}
|
||||
if (results.isNotEmpty) {
|
||||
final share = AndroidFileShare(
|
||||
results.map((e) => e.item2 as String).toList(),
|
||||
results.map((e) => e.item1.fdMime).toList(),
|
||||
);
|
||||
final share = AndroidFileShare(results
|
||||
.map((e) => AndroidFileShareFile(e.item2 as String, e.item1.fdMime))
|
||||
.toList());
|
||||
unawaited(share.share());
|
||||
}
|
||||
emit(state.copyWith(result: true));
|
||||
|
@ -129,10 +128,9 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
|||
}
|
||||
}
|
||||
if (results.isNotEmpty) {
|
||||
final share = AndroidFileShare(
|
||||
results.map((e) => e.item2 as String).toList(),
|
||||
results.map((e) => e.item1.fdMime).toList(),
|
||||
);
|
||||
final share = AndroidFileShare(results
|
||||
.map((e) => AndroidFileShareFile(e.item2 as String, e.item1.fdMime))
|
||||
.toList());
|
||||
unawaited(share.share());
|
||||
}
|
||||
emit(state.copyWith(result: true));
|
||||
|
|
44
app/lib/widget/set_as_method_dialog.dart
Normal file
44
app/lib/widget/set_as_method_dialog.dart
Normal file
|
@ -0,0 +1,44 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:nc_photos/app_localizations.dart';
|
||||
|
||||
enum SetAsMethod {
|
||||
file,
|
||||
preview,
|
||||
}
|
||||
|
||||
class SetAsMethodDialog extends StatelessWidget {
|
||||
const SetAsMethodDialog({
|
||||
super.key,
|
||||
this.isSupportPerview = true,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SimpleDialog(
|
||||
title: Text(L10n.global().setAsTooltip),
|
||||
children: [
|
||||
if (isSupportPerview)
|
||||
SimpleDialogOption(
|
||||
child: ListTile(
|
||||
title: Text(L10n.global().shareMethodPreviewTitle),
|
||||
subtitle: Text(L10n.global().shareMethodPreviewDescription),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(SetAsMethod.preview);
|
||||
},
|
||||
),
|
||||
SimpleDialogOption(
|
||||
child: ListTile(
|
||||
title: Text(L10n.global().shareMethodOriginalFileTitle),
|
||||
subtitle: Text(L10n.global().shareMethodOriginalFileDescription),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(SetAsMethod.file);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
final bool isSupportPerview;
|
||||
}
|
|
@ -18,12 +18,14 @@ import 'package:nc_photos/entity/collection_item.dart';
|
|||
import 'package:nc_photos/entity/exif_extension.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/k.dart' as k;
|
||||
import 'package:nc_photos/location_util.dart' as location_util;
|
||||
import 'package:nc_photos/object_extension.dart';
|
||||
import 'package:nc_photos/or_null.dart';
|
||||
import 'package:nc_photos/platform/features.dart' as features;
|
||||
import 'package:nc_photos/platform/k.dart' as platform_k;
|
||||
import 'package:nc_photos/set_as_handler.dart';
|
||||
import 'package:nc_photos/snack_bar_manager.dart';
|
||||
import 'package:nc_photos/theme.dart';
|
||||
import 'package:nc_photos/use_case/inflate_file_descriptor.dart';
|
||||
|
@ -169,6 +171,13 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
|||
label: L10n.global().addItemToCollectionTooltip,
|
||||
onPressed: () => _onAddToAlbumPressed(context),
|
||||
),
|
||||
if (platform_k.isAndroid &&
|
||||
file_util.isSupportedImageFormat(_file!))
|
||||
_DetailPaneButton(
|
||||
icon: Icons.launch,
|
||||
label: L10n.global().setAsTooltip,
|
||||
onPressed: () => _onSetAsPressed(context),
|
||||
),
|
||||
if (widget.fd.fdIsArchived == true)
|
||||
_DetailPaneButton(
|
||||
icon: Icons.unarchive_outlined,
|
||||
|
@ -417,6 +426,12 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
|||
);
|
||||
}
|
||||
|
||||
void _onSetAsPressed(BuildContext context) {
|
||||
assert(_file != null);
|
||||
final c = KiwiContainer().resolve<DiContainer>();
|
||||
SetAsHandler(c, context: context).setAsFile(widget.account, _file!);
|
||||
}
|
||||
|
||||
void _onMapTap() {
|
||||
if (platform_k.isAndroid) {
|
||||
final intent = AndroidIntent(
|
||||
|
|
|
@ -11,4 +11,5 @@
|
|||
<string name="download_progress_notification_untitled_text">Downloading</string>
|
||||
<string name="log_save_successful_notification_title">Logs saved successfully</string>
|
||||
<string name="log_save_successful_notification_text">Tap to view your saved logs</string>
|
||||
<string name="attach_data_chooser_title">Set as</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in a new issue