mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-01-22 16:56:19 +01:00
Support sharing a reduced quality preview
This commit is contained in:
parent
3f3f5b9f81
commit
c47880be37
10 changed files with 202 additions and 16 deletions
|
@ -1,6 +1,7 @@
|
|||
package com.nkming.nc_photos
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ClipData
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
|
@ -54,14 +55,26 @@ class ShareChannelHandler(activity: Activity) :
|
|||
val shareIntent = if (uris.size == 1) Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(Intent.EXTRA_STREAM, uris[0])
|
||||
// setting clipdata is needed for FLAG_GRANT_READ_URI_PERMISSION to
|
||||
// work, see: https://developer.android.com/reference/android/content/Intent#ACTION_SEND
|
||||
clipData =
|
||||
ClipData.newUri(_context.contentResolver, "Share", uris[0])
|
||||
type = mimeTypes[0] ?: "*/*"
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
} else Intent().apply {
|
||||
action = Intent.ACTION_SEND_MULTIPLE
|
||||
putParcelableArrayListExtra(Intent.EXTRA_STREAM, ArrayList(uris))
|
||||
type =
|
||||
if (mimeTypes.all { it?.startsWith("image/") == true }) "image/*" else "*/*"
|
||||
clipData =
|
||||
ClipData.newUri(_context.contentResolver, "Share", uris[0])
|
||||
.apply {
|
||||
for (uri in uris.subList(1, uris.size)) {
|
||||
addItem(ClipData.Item(uri))
|
||||
}
|
||||
}
|
||||
type = if (mimeTypes.all {
|
||||
it?.startsWith("image/") == true
|
||||
}) "image/*" else "*/*"
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<external-path name="downloads" path="Download"/>
|
||||
<cache-path name="previews" path="largeImageCache"/>
|
||||
</paths>
|
||||
|
|
|
@ -51,6 +51,7 @@ class ThumbnailCacheManager {
|
|||
/// very large in size. Since large images are only viewed one by one (unlike
|
||||
/// thumbnails), they are less critical to the overall app responsiveness
|
||||
class LargeImageCacheManager {
|
||||
// used in file_paths.xml, must not change
|
||||
static const key = "largeImageCache";
|
||||
static CacheManager inst = CacheManager(
|
||||
Config(
|
||||
|
|
|
@ -906,11 +906,16 @@
|
|||
"@shareMethodDialogTitle": {
|
||||
"description": "Let the user pick how they want to share"
|
||||
},
|
||||
"shareMethodFileTitle": "File",
|
||||
"@shareMethodFileTitle": {
|
||||
"description": "Share the actual file"
|
||||
"shareMethodPreviewTitle": "Preview",
|
||||
"@shareMethodPreviewTitle": {
|
||||
"description": "Share the preview of a file"
|
||||
},
|
||||
"shareMethodFileDescription": "Download the file and share it to other apps",
|
||||
"shareMethodPreviewDescription": "Share a reduced quality preview to other apps (only support images)",
|
||||
"shareMethodOriginalFileTitle": "Original file",
|
||||
"@shareMethodOriginalFileTitle": {
|
||||
"description": "Share the original file"
|
||||
},
|
||||
"shareMethodOriginalFileDescription": "Download the original file and share it to other apps",
|
||||
"shareMethodPublicLinkTitle": "Public link",
|
||||
"@shareMethodPublicLinkTitle": {
|
||||
"description": "Create a share link on server and share it"
|
||||
|
|
|
@ -41,6 +41,10 @@
|
|||
"sortOptionFilenameDescendingLabel",
|
||||
"sortOptionManualLabel",
|
||||
"helpButtonLabel",
|
||||
"shareMethodPreviewTitle",
|
||||
"shareMethodPreviewDescription",
|
||||
"shareMethodOriginalFileTitle",
|
||||
"shareMethodOriginalFileDescription",
|
||||
"collectionSharingLabel",
|
||||
"fileLastSharedDescription",
|
||||
"fileLastSharedByOthersDescription",
|
||||
|
@ -210,8 +214,10 @@
|
|||
"slideshowSetupDialogRepeatTitle",
|
||||
"linkCopiedNotification",
|
||||
"shareMethodDialogTitle",
|
||||
"shareMethodFileTitle",
|
||||
"shareMethodFileDescription",
|
||||
"shareMethodPreviewTitle",
|
||||
"shareMethodPreviewDescription",
|
||||
"shareMethodOriginalFileTitle",
|
||||
"shareMethodOriginalFileDescription",
|
||||
"shareMethodPublicLinkTitle",
|
||||
"shareMethodPublicLinkDescription",
|
||||
"shareMethodPasswordLinkTitle",
|
||||
|
@ -346,6 +352,10 @@
|
|||
"settingsMemoriesRangeTitle",
|
||||
"settingsMemoriesRangeValueText",
|
||||
"settingsDoubleTapExitTitle",
|
||||
"shareMethodPreviewTitle",
|
||||
"shareMethodPreviewDescription",
|
||||
"shareMethodOriginalFileTitle",
|
||||
"shareMethodOriginalFileDescription",
|
||||
"enhanceStyleTransferStyleDialogTitle",
|
||||
"doubleTapExitNotification",
|
||||
"imageEditDiscardDialogTitle",
|
||||
|
@ -397,6 +407,10 @@
|
|||
"settingsMemoriesRangeTitle",
|
||||
"settingsMemoriesRangeValueText",
|
||||
"rootPickerSkipConfirmationDialogContent2",
|
||||
"shareMethodPreviewTitle",
|
||||
"shareMethodPreviewDescription",
|
||||
"shareMethodOriginalFileTitle",
|
||||
"shareMethodOriginalFileDescription",
|
||||
"imageEditToolbarColorLabel",
|
||||
"imageEditToolbarTransformLabel",
|
||||
"imageEditTransformOrientation",
|
||||
|
@ -415,6 +429,10 @@
|
|||
"settingsPhotosPageTitle",
|
||||
"settingsMemoriesRangeTitle",
|
||||
"settingsMemoriesRangeValueText",
|
||||
"shareMethodPreviewTitle",
|
||||
"shareMethodPreviewDescription",
|
||||
"shareMethodOriginalFileTitle",
|
||||
"shareMethodOriginalFileDescription",
|
||||
"imageEditToolbarColorLabel",
|
||||
"imageEditToolbarTransformLabel",
|
||||
"imageEditTransformOrientation",
|
||||
|
@ -451,6 +469,10 @@
|
|||
"helpTooltip",
|
||||
"helpButtonLabel",
|
||||
"removeFromAlbumTooltip",
|
||||
"shareMethodPreviewTitle",
|
||||
"shareMethodPreviewDescription",
|
||||
"shareMethodOriginalFileTitle",
|
||||
"shareMethodOriginalFileDescription",
|
||||
"enhanceTooltip",
|
||||
"enhanceButtonLabel",
|
||||
"enhanceIntroDialogTitle",
|
||||
|
@ -527,6 +549,10 @@
|
|||
"settingsPhotosTabSortByNameTitle",
|
||||
"sortOptionFilenameAscendingLabel",
|
||||
"sortOptionFilenameDescendingLabel",
|
||||
"shareMethodPreviewTitle",
|
||||
"shareMethodPreviewDescription",
|
||||
"shareMethodOriginalFileTitle",
|
||||
"shareMethodOriginalFileDescription",
|
||||
"createCollectionTooltip",
|
||||
"createCollectionDialogAlbumLabel",
|
||||
"createCollectionDialogAlbumDescription",
|
||||
|
@ -624,6 +650,10 @@
|
|||
"settingsPhotosTabSortByNameTitle",
|
||||
"sortOptionFilenameAscendingLabel",
|
||||
"sortOptionFilenameDescendingLabel",
|
||||
"shareMethodPreviewTitle",
|
||||
"shareMethodPreviewDescription",
|
||||
"shareMethodOriginalFileTitle",
|
||||
"shareMethodOriginalFileDescription",
|
||||
"enhanceTooltip",
|
||||
"enhanceButtonLabel",
|
||||
"enhanceIntroDialogTitle",
|
||||
|
@ -700,6 +730,10 @@
|
|||
"settingsPhotosTabSortByNameTitle",
|
||||
"sortOptionFilenameAscendingLabel",
|
||||
"sortOptionFilenameDescendingLabel",
|
||||
"shareMethodPreviewTitle",
|
||||
"shareMethodPreviewDescription",
|
||||
"shareMethodOriginalFileTitle",
|
||||
"shareMethodOriginalFileDescription",
|
||||
"enhanceTooltip",
|
||||
"enhanceButtonLabel",
|
||||
"enhanceIntroDialogTitle",
|
||||
|
@ -776,6 +810,10 @@
|
|||
"settingsPhotosTabSortByNameTitle",
|
||||
"sortOptionFilenameAscendingLabel",
|
||||
"sortOptionFilenameDescendingLabel",
|
||||
"shareMethodPreviewTitle",
|
||||
"shareMethodPreviewDescription",
|
||||
"shareMethodOriginalFileTitle",
|
||||
"shareMethodOriginalFileDescription",
|
||||
"enhanceTooltip",
|
||||
"enhanceButtonLabel",
|
||||
"enhanceIntroDialogTitle",
|
||||
|
@ -852,6 +890,10 @@
|
|||
"settingsPhotosTabSortByNameTitle",
|
||||
"sortOptionFilenameAscendingLabel",
|
||||
"sortOptionFilenameDescendingLabel",
|
||||
"shareMethodPreviewTitle",
|
||||
"shareMethodPreviewDescription",
|
||||
"shareMethodOriginalFileTitle",
|
||||
"shareMethodOriginalFileDescription",
|
||||
"enhanceTooltip",
|
||||
"enhanceButtonLabel",
|
||||
"enhanceIntroDialogTitle",
|
||||
|
|
|
@ -10,6 +10,7 @@ import 'package:nc_photos/app_localizations.dart';
|
|||
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_util.dart' as file_util;
|
||||
import 'package:nc_photos/entity/local_file.dart';
|
||||
import 'package:nc_photos/entity/share.dart';
|
||||
import 'package:nc_photos/entity/share/data_source.dart';
|
||||
|
@ -24,6 +25,7 @@ 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/share_local.dart';
|
||||
import 'package:nc_photos/widget/processing_dialog.dart';
|
||||
import 'package:nc_photos/widget/share_link_multiple_files_dialog.dart';
|
||||
|
@ -67,7 +69,7 @@ class ShareHandler {
|
|||
|
||||
Future<void> shareFiles(Account account, List<File> files) async {
|
||||
try {
|
||||
final method = await _askShareMethod();
|
||||
final method = await _askShareMethod(files);
|
||||
if (method == null) {
|
||||
// user canceled
|
||||
return;
|
||||
|
@ -75,6 +77,8 @@ class ShareHandler {
|
|||
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);
|
||||
} else {
|
||||
return await _shareAsFile(account, files);
|
||||
}
|
||||
|
@ -91,9 +95,59 @@ class ShareHandler {
|
|||
}
|
||||
}
|
||||
|
||||
Future<ShareMethod?> _askShareMethod() {
|
||||
Future<ShareMethod?> _askShareMethod(List<File> files) {
|
||||
return showDialog<ShareMethod>(
|
||||
context: context, builder: (context) => const ShareMethodDialog());
|
||||
context: context,
|
||||
builder: (context) => ShareMethodDialog(
|
||||
isSupportPerview: files.any((f) => file_util.isSupportedImageFormat(f)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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());
|
||||
return share.share();
|
||||
}
|
||||
|
||||
Future<void> _shareAsFile(Account account, List<File> files) async {
|
||||
|
|
26
app/lib/use_case/download_preview.dart
Normal file
26
app/lib/use_case/download_preview.dart
Normal file
|
@ -0,0 +1,26 @@
|
|||
import 'package:nc_photos/account.dart';
|
||||
import 'package:nc_photos/api/api.dart';
|
||||
import 'package:nc_photos/api/api_util.dart' as api_util;
|
||||
import 'package:nc_photos/cache_manager_util.dart';
|
||||
import 'package:nc_photos/entity/file.dart';
|
||||
import 'package:nc_photos/k.dart' as k;
|
||||
import 'package:nc_photos/platform/k.dart' as platform_k;
|
||||
import 'package:nc_photos_plugin/nc_photos_plugin.dart';
|
||||
|
||||
class DownloadPreview {
|
||||
Future<dynamic> call(Account account, File file) async {
|
||||
assert(platform_k.isAndroid);
|
||||
final previewUrl = api_util.getFilePreviewUrl(
|
||||
account,
|
||||
file,
|
||||
width: k.photoLargeSize,
|
||||
height: k.photoLargeSize,
|
||||
a: true,
|
||||
);
|
||||
final fileInfo =
|
||||
await LargeImageCacheManager.inst.getSingleFile(previewUrl, headers: {
|
||||
"authorization": Api.getAuthorizationHeaderValue(account),
|
||||
});
|
||||
return ContentUri.getUriForFile(fileInfo.absolute.path);
|
||||
}
|
||||
}
|
|
@ -4,30 +4,43 @@ import 'package:nc_photos/platform/k.dart' as platform_k;
|
|||
|
||||
enum ShareMethod {
|
||||
file,
|
||||
preview,
|
||||
publicLink,
|
||||
passwordLink,
|
||||
}
|
||||
|
||||
class ShareMethodDialog extends StatelessWidget {
|
||||
const ShareMethodDialog({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
super.key,
|
||||
required this.isSupportPerview,
|
||||
});
|
||||
|
||||
@override
|
||||
build(BuildContext context) {
|
||||
return SimpleDialog(
|
||||
title: Text(L10n.global().shareMethodDialogTitle),
|
||||
children: [
|
||||
if (platform_k.isAndroid)
|
||||
if (platform_k.isAndroid) ...[
|
||||
if (isSupportPerview)
|
||||
SimpleDialogOption(
|
||||
child: ListTile(
|
||||
title: Text(L10n.global().shareMethodPreviewTitle),
|
||||
subtitle: Text(L10n.global().shareMethodPreviewDescription),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(ShareMethod.preview);
|
||||
},
|
||||
),
|
||||
SimpleDialogOption(
|
||||
child: ListTile(
|
||||
title: Text(L10n.global().shareMethodFileTitle),
|
||||
subtitle: Text(L10n.global().shareMethodFileDescription),
|
||||
title: Text(L10n.global().shareMethodOriginalFileTitle),
|
||||
subtitle: Text(L10n.global().shareMethodOriginalFileDescription),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(ShareMethod.file);
|
||||
},
|
||||
),
|
||||
],
|
||||
SimpleDialogOption(
|
||||
child: ListTile(
|
||||
title: Text(L10n.global().shareMethodPublicLinkTitle),
|
||||
|
@ -49,4 +62,6 @@ class ShareMethodDialog extends StatelessWidget {
|
|||
],
|
||||
);
|
||||
}
|
||||
|
||||
final bool isSupportPerview;
|
||||
}
|
||||
|
|
|
@ -2,8 +2,10 @@ package com.nkming.nc_photos.plugin
|
|||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.core.content.FileProvider
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
|
||||
class ContentUriChannelHandler(context: Context) :
|
||||
|
@ -24,6 +26,14 @@ class ContentUriChannelHandler(context: Context) :
|
|||
}
|
||||
}
|
||||
|
||||
"getUriForFile" -> {
|
||||
try {
|
||||
getUriForFile(call.argument("filePath")!!, result)
|
||||
} catch (e: Throwable) {
|
||||
result.error("systemException", e.toString(), null)
|
||||
}
|
||||
}
|
||||
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
@ -46,5 +56,18 @@ class ContentUriChannelHandler(context: Context) :
|
|||
}
|
||||
}
|
||||
|
||||
private fun getUriForFile(filePath: String, result: MethodChannel.Result) {
|
||||
try {
|
||||
val file = File(filePath)
|
||||
val contentUri = FileProvider.getUriForFile(
|
||||
context, "${context.packageName}.fileprovider", file
|
||||
)
|
||||
result.success(contentUri.toString())
|
||||
} catch (e: IllegalArgumentException) {
|
||||
logE(TAG, "[getUriForFile] Unsupported file path: $filePath")
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
private val context = context
|
||||
}
|
||||
|
|
|
@ -20,6 +20,12 @@ class ContentUri {
|
|||
}
|
||||
}
|
||||
|
||||
static Future<String> getUriForFile(String filePath) async {
|
||||
return await _methodChannel.invokeMethod("getUriForFile", <String, dynamic>{
|
||||
"filePath": filePath,
|
||||
});
|
||||
}
|
||||
|
||||
static const _methodChannel = MethodChannel("${k.libId}/content_uri_method");
|
||||
|
||||
static const _exceptionFileNotFound = "fileNotFoundException";
|
||||
|
|
Loading…
Reference in a new issue