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 {
|
try {
|
||||||
shareItems(
|
shareItems(
|
||||||
call.argument("fileUris")!!,
|
call.argument("fileUris")!!,
|
||||||
call.argument("mimeTypes")!!,
|
call.argument("mimeTypes")!!, result
|
||||||
result
|
|
||||||
)
|
)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
result.error("systemException", e.toString(), null)
|
result.error("systemException", e.toString(), null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"shareText" -> {
|
"shareText" -> {
|
||||||
try {
|
try {
|
||||||
shareText(
|
shareText(
|
||||||
call.argument("text")!!,
|
call.argument("text")!!, call.argument("mimeType"),
|
||||||
call.argument("mimeType"),
|
|
||||||
result
|
result
|
||||||
)
|
)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
result.error("systemException", e.toString(), null)
|
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 -> {
|
else -> {
|
||||||
result.notImplemented()
|
result.notImplemented()
|
||||||
}
|
}
|
||||||
|
@ -44,8 +55,7 @@ class ShareChannelHandler(activity: Activity) :
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun shareItems(
|
private fun shareItems(
|
||||||
fileUris: List<String>,
|
fileUris: List<String>, mimeTypes: List<String?>,
|
||||||
mimeTypes: List<String?>,
|
|
||||||
result: MethodChannel.Result
|
result: MethodChannel.Result
|
||||||
) {
|
) {
|
||||||
assert(fileUris.isNotEmpty())
|
assert(fileUris.isNotEmpty())
|
||||||
|
@ -104,6 +114,26 @@ class ShareChannelHandler(activity: Activity) :
|
||||||
result.success(null)
|
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 _activity = activity
|
||||||
private val _context get() = _activity
|
private val _context get() = _activity
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,8 +53,8 @@ class LocalFileMediaStoreDataSource implements LocalFileDataSource {
|
||||||
onFailure?.call(f, ArgumentError("File not supported"), null);
|
onFailure?.call(f, ArgumentError("File not supported"), null);
|
||||||
});
|
});
|
||||||
|
|
||||||
final share = AndroidFileShare(uriFiles.map((e) => e.uri).toList(),
|
final share = AndroidFileShare(
|
||||||
uriFiles.map((e) => e.mime).toList());
|
uriFiles.map((e) => AndroidFileShareFile(e.uri, e.mime)).toList());
|
||||||
try {
|
try {
|
||||||
await share.share();
|
await share.share();
|
||||||
} catch (e, stackTrace) {
|
} 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",
|
"removeCollectionsFailedNotification": "Failed to remove some collections",
|
||||||
"accountSettingsTooltip": "Account settings",
|
"accountSettingsTooltip": "Account settings",
|
||||||
"contributorsTooltip": "Contributors",
|
"contributorsTooltip": "Contributors",
|
||||||
|
"setAsTooltip": "Set as",
|
||||||
|
"@setAsTooltip": {
|
||||||
|
"description": "e.g., set as wallpaper"
|
||||||
|
},
|
||||||
|
|
||||||
"errorUnauthenticated": "Unauthenticated access. Please sign-in again if the problem continues",
|
"errorUnauthenticated": "Unauthenticated access. Please sign-in again if the problem continues",
|
||||||
"@errorUnauthenticated": {
|
"@errorUnauthenticated": {
|
||||||
|
|
|
@ -17,7 +17,8 @@
|
||||||
"createCollectionDialogNextcloudAlbumDescription",
|
"createCollectionDialogNextcloudAlbumDescription",
|
||||||
"removeCollectionsFailedNotification",
|
"removeCollectionsFailedNotification",
|
||||||
"accountSettingsTooltip",
|
"accountSettingsTooltip",
|
||||||
"contributorsTooltip"
|
"contributorsTooltip",
|
||||||
|
"setAsTooltip"
|
||||||
],
|
],
|
||||||
|
|
||||||
"de": [
|
"de": [
|
||||||
|
@ -106,6 +107,7 @@
|
||||||
"removeCollectionsFailedNotification",
|
"removeCollectionsFailedNotification",
|
||||||
"accountSettingsTooltip",
|
"accountSettingsTooltip",
|
||||||
"contributorsTooltip",
|
"contributorsTooltip",
|
||||||
|
"setAsTooltip",
|
||||||
"errorAlbumDowngrade"
|
"errorAlbumDowngrade"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -216,7 +218,8 @@
|
||||||
"createCollectionDialogNextcloudAlbumDescription",
|
"createCollectionDialogNextcloudAlbumDescription",
|
||||||
"removeCollectionsFailedNotification",
|
"removeCollectionsFailedNotification",
|
||||||
"accountSettingsTooltip",
|
"accountSettingsTooltip",
|
||||||
"contributorsTooltip"
|
"contributorsTooltip",
|
||||||
|
"setAsTooltip"
|
||||||
],
|
],
|
||||||
|
|
||||||
"es": [
|
"es": [
|
||||||
|
@ -226,7 +229,8 @@
|
||||||
"settingsSeedColorPickerSystemColorButtonLabel",
|
"settingsSeedColorPickerSystemColorButtonLabel",
|
||||||
"searchLandingPeopleListEmptyText2",
|
"searchLandingPeopleListEmptyText2",
|
||||||
"accountSettingsTooltip",
|
"accountSettingsTooltip",
|
||||||
"contributorsTooltip"
|
"contributorsTooltip",
|
||||||
|
"setAsTooltip"
|
||||||
],
|
],
|
||||||
|
|
||||||
"fi": [
|
"fi": [
|
||||||
|
@ -236,7 +240,8 @@
|
||||||
"settingsSeedColorPickerSystemColorButtonLabel",
|
"settingsSeedColorPickerSystemColorButtonLabel",
|
||||||
"searchLandingPeopleListEmptyText2",
|
"searchLandingPeopleListEmptyText2",
|
||||||
"accountSettingsTooltip",
|
"accountSettingsTooltip",
|
||||||
"contributorsTooltip"
|
"contributorsTooltip",
|
||||||
|
"setAsTooltip"
|
||||||
],
|
],
|
||||||
|
|
||||||
"fr": [
|
"fr": [
|
||||||
|
@ -366,7 +371,8 @@
|
||||||
"createCollectionDialogNextcloudAlbumDescription",
|
"createCollectionDialogNextcloudAlbumDescription",
|
||||||
"removeCollectionsFailedNotification",
|
"removeCollectionsFailedNotification",
|
||||||
"accountSettingsTooltip",
|
"accountSettingsTooltip",
|
||||||
"contributorsTooltip"
|
"contributorsTooltip",
|
||||||
|
"setAsTooltip"
|
||||||
],
|
],
|
||||||
|
|
||||||
"it": [
|
"it": [
|
||||||
|
@ -671,6 +677,7 @@
|
||||||
"removeCollectionsFailedNotification",
|
"removeCollectionsFailedNotification",
|
||||||
"accountSettingsTooltip",
|
"accountSettingsTooltip",
|
||||||
"contributorsTooltip",
|
"contributorsTooltip",
|
||||||
|
"setAsTooltip",
|
||||||
"errorUnauthenticated",
|
"errorUnauthenticated",
|
||||||
"errorDisconnected",
|
"errorDisconnected",
|
||||||
"errorLocked",
|
"errorLocked",
|
||||||
|
@ -1021,6 +1028,7 @@
|
||||||
"removeCollectionsFailedNotification",
|
"removeCollectionsFailedNotification",
|
||||||
"accountSettingsTooltip",
|
"accountSettingsTooltip",
|
||||||
"contributorsTooltip",
|
"contributorsTooltip",
|
||||||
|
"setAsTooltip",
|
||||||
"errorUnauthenticated",
|
"errorUnauthenticated",
|
||||||
"errorDisconnected",
|
"errorDisconnected",
|
||||||
"errorLocked",
|
"errorLocked",
|
||||||
|
@ -1172,7 +1180,8 @@
|
||||||
"createCollectionDialogNextcloudAlbumDescription",
|
"createCollectionDialogNextcloudAlbumDescription",
|
||||||
"removeCollectionsFailedNotification",
|
"removeCollectionsFailedNotification",
|
||||||
"accountSettingsTooltip",
|
"accountSettingsTooltip",
|
||||||
"contributorsTooltip"
|
"contributorsTooltip",
|
||||||
|
"setAsTooltip"
|
||||||
],
|
],
|
||||||
|
|
||||||
"pt": [
|
"pt": [
|
||||||
|
@ -1193,7 +1202,8 @@
|
||||||
"createCollectionDialogNextcloudAlbumDescription",
|
"createCollectionDialogNextcloudAlbumDescription",
|
||||||
"removeCollectionsFailedNotification",
|
"removeCollectionsFailedNotification",
|
||||||
"accountSettingsTooltip",
|
"accountSettingsTooltip",
|
||||||
"contributorsTooltip"
|
"contributorsTooltip",
|
||||||
|
"setAsTooltip"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ru": [
|
"ru": [
|
||||||
|
@ -1319,7 +1329,8 @@
|
||||||
"createCollectionDialogNextcloudAlbumDescription",
|
"createCollectionDialogNextcloudAlbumDescription",
|
||||||
"removeCollectionsFailedNotification",
|
"removeCollectionsFailedNotification",
|
||||||
"accountSettingsTooltip",
|
"accountSettingsTooltip",
|
||||||
"contributorsTooltip"
|
"contributorsTooltip",
|
||||||
|
"setAsTooltip"
|
||||||
],
|
],
|
||||||
|
|
||||||
"zh": [
|
"zh": [
|
||||||
|
@ -1445,7 +1456,8 @@
|
||||||
"createCollectionDialogNextcloudAlbumDescription",
|
"createCollectionDialogNextcloudAlbumDescription",
|
||||||
"removeCollectionsFailedNotification",
|
"removeCollectionsFailedNotification",
|
||||||
"accountSettingsTooltip",
|
"accountSettingsTooltip",
|
||||||
"contributorsTooltip"
|
"contributorsTooltip",
|
||||||
|
"setAsTooltip"
|
||||||
],
|
],
|
||||||
|
|
||||||
"zh_Hant": [
|
"zh_Hant": [
|
||||||
|
@ -1571,6 +1583,7 @@
|
||||||
"createCollectionDialogNextcloudAlbumDescription",
|
"createCollectionDialogNextcloudAlbumDescription",
|
||||||
"removeCollectionsFailedNotification",
|
"removeCollectionsFailedNotification",
|
||||||
"accountSettingsTooltip",
|
"accountSettingsTooltip",
|
||||||
"contributorsTooltip"
|
"contributorsTooltip",
|
||||||
|
"setAsTooltip"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,17 @@ class Share {
|
||||||
"mimeTypes": mimeTypes,
|
"mimeTypes": mimeTypes,
|
||||||
});
|
});
|
||||||
|
|
||||||
static Future<void> shareText(
|
static Future<void> shareText(String text, String? mimeType) =>
|
||||||
String text, String? mimeType) =>
|
|
||||||
_channel.invokeMethod("shareText", <String, dynamic>{
|
_channel.invokeMethod("shareText", <String, dynamic>{
|
||||||
"text": text,
|
"text": text,
|
||||||
"mimeType": mimeType,
|
"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");
|
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/mobile/android/share.dart';
|
||||||
import 'package:nc_photos/platform/share.dart' as itf;
|
import 'package:nc_photos/platform/share.dart' as itf;
|
||||||
|
|
||||||
class AndroidFileShare extends itf.FileShare {
|
class AndroidFileShareFile {
|
||||||
AndroidFileShare(this.fileUris, this.mimeTypes);
|
const AndroidFileShareFile(this.fileUri, this.mimeType);
|
||||||
|
|
||||||
|
final String fileUri;
|
||||||
|
final String? mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AndroidFileShare implements itf.FileShare {
|
||||||
|
const AndroidFileShare(this.files);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
share() {
|
Future<void> share() {
|
||||||
return Share.shareItems(fileUris, mimeTypes);
|
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;
|
Future<void> setAs() {
|
||||||
final List<String?> mimeTypes;
|
assert(files.length == 1);
|
||||||
|
return Share.shareAsAttachData(files.first.fileUri, files.first.mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<AndroidFileShareFile> files;
|
||||||
}
|
}
|
||||||
|
|
||||||
class AndroidTextShare extends itf.TextShare {
|
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.dart';
|
||||||
import 'package:nc_photos/entity/share/data_source.dart';
|
import 'package:nc_photos/entity/share/data_source.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/iterable_extension.dart';
|
import 'package:nc_photos/internal_download_handler.dart';
|
||||||
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/k.dart' as platform_k;
|
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/copy.dart';
|
||||||
import 'package:nc_photos/use_case/create_dir.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/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/inflate_file_descriptor.dart';
|
||||||
import 'package:nc_photos/use_case/share_local.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_link_multiple_files_dialog.dart';
|
||||||
import 'package:nc_photos/widget/share_method_dialog.dart';
|
import 'package:nc_photos/widget/share_method_dialog.dart';
|
||||||
import 'package:nc_photos/widget/simple_input_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_codegen/np_codegen.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
|
||||||
|
|
||||||
part 'share_handler.g.dart';
|
part 'share_handler.g.dart';
|
||||||
|
|
||||||
|
@ -118,100 +113,21 @@ class ShareHandler {
|
||||||
|
|
||||||
Future<void> _shareAsPreview(Account account, List<File> files) async {
|
Future<void> _shareAsPreview(Account account, List<File> files) async {
|
||||||
assert(platform_k.isAndroid);
|
assert(platform_k.isAndroid);
|
||||||
final controller = StreamController<String>();
|
final results =
|
||||||
unawaited(
|
await InternalDownloadHandler(account).downloadPreviews(context, files);
|
||||||
showDialog(
|
final share = AndroidFileShare(results.entries
|
||||||
context: context,
|
.map((e) => AndroidFileShareFile(e.value as String, e.key.contentType))
|
||||||
builder: (context) => StreamBuilder(
|
.toList());
|
||||||
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());
|
|
||||||
return share.share();
|
return share.share();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _shareAsFile(Account account, List<File> files) async {
|
Future<void> _shareAsFile(Account account, List<File> files) async {
|
||||||
assert(platform_k.isAndroid);
|
assert(platform_k.isAndroid);
|
||||||
final controller = StreamController<String>();
|
final results =
|
||||||
unawaited(
|
await InternalDownloadHandler(account).downloadFiles(context, files);
|
||||||
showDialog(
|
final share = AndroidFileShare(results.entries
|
||||||
context: context,
|
.map((e) => AndroidFileShareFile(e.value as String, e.key.contentType))
|
||||||
builder: (context) => StreamBuilder(
|
.toList());
|
||||||
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());
|
|
||||||
return share.share();
|
return share.share();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -94,10 +94,9 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (results.isNotEmpty) {
|
if (results.isNotEmpty) {
|
||||||
final share = AndroidFileShare(
|
final share = AndroidFileShare(results
|
||||||
results.map((e) => e.item2 as String).toList(),
|
.map((e) => AndroidFileShareFile(e.item2 as String, e.item1.fdMime))
|
||||||
results.map((e) => e.item1.fdMime).toList(),
|
.toList());
|
||||||
);
|
|
||||||
unawaited(share.share());
|
unawaited(share.share());
|
||||||
}
|
}
|
||||||
emit(state.copyWith(result: true));
|
emit(state.copyWith(result: true));
|
||||||
|
@ -129,10 +128,9 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (results.isNotEmpty) {
|
if (results.isNotEmpty) {
|
||||||
final share = AndroidFileShare(
|
final share = AndroidFileShare(results
|
||||||
results.map((e) => e.item2 as String).toList(),
|
.map((e) => AndroidFileShareFile(e.item2 as String, e.item1.fdMime))
|
||||||
results.map((e) => e.item1.fdMime).toList(),
|
.toList());
|
||||||
);
|
|
||||||
unawaited(share.share());
|
unawaited(share.share());
|
||||||
}
|
}
|
||||||
emit(state.copyWith(result: true));
|
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/exif_extension.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/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/location_util.dart' as location_util;
|
import 'package:nc_photos/location_util.dart' as location_util;
|
||||||
import 'package:nc_photos/object_extension.dart';
|
import 'package:nc_photos/object_extension.dart';
|
||||||
import 'package:nc_photos/or_null.dart';
|
import 'package:nc_photos/or_null.dart';
|
||||||
import 'package:nc_photos/platform/features.dart' as features;
|
import 'package:nc_photos/platform/features.dart' as features;
|
||||||
import 'package:nc_photos/platform/k.dart' as platform_k;
|
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/snack_bar_manager.dart';
|
||||||
import 'package:nc_photos/theme.dart';
|
import 'package:nc_photos/theme.dart';
|
||||||
import 'package:nc_photos/use_case/inflate_file_descriptor.dart';
|
import 'package:nc_photos/use_case/inflate_file_descriptor.dart';
|
||||||
|
@ -169,6 +171,13 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
||||||
label: L10n.global().addItemToCollectionTooltip,
|
label: L10n.global().addItemToCollectionTooltip,
|
||||||
onPressed: () => _onAddToAlbumPressed(context),
|
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)
|
if (widget.fd.fdIsArchived == true)
|
||||||
_DetailPaneButton(
|
_DetailPaneButton(
|
||||||
icon: Icons.unarchive_outlined,
|
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() {
|
void _onMapTap() {
|
||||||
if (platform_k.isAndroid) {
|
if (platform_k.isAndroid) {
|
||||||
final intent = AndroidIntent(
|
final intent = AndroidIntent(
|
||||||
|
|
|
@ -11,4 +11,5 @@
|
||||||
<string name="download_progress_notification_untitled_text">Downloading</string>
|
<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_title">Logs saved successfully</string>
|
||||||
<string name="log_save_successful_notification_text">Tap to view your saved logs</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>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue