diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt index 14b66a9c1..69e390997 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt @@ -1,8 +1,6 @@ package deckers.thibault.aves.channel.calls -import android.content.ContentResolver -import android.content.Context -import android.content.Intent +import android.content.* import android.content.pm.ApplicationInfo import android.content.res.Configuration import android.net.Uri @@ -32,6 +30,7 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler { when (call.method) { "getPackages" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getPackages) } "getAppIcon" -> GlobalScope.launch(Dispatchers.IO) { safesus(call, result, ::getAppIcon) } + "copyToClipboard" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::copyToClipboard) } "edit" -> { val title = call.argument("title") val uri = call.argument("uri")?.let { Uri.parse(it) } @@ -156,6 +155,24 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler { } } + private fun copyToClipboard(call: MethodCall, result: MethodChannel.Result) { + val uri = call.argument("uri")?.let { Uri.parse(it) } + val label = call.argument("label") + if (uri == null) { + result.error("copyToClipboard-args", "failed because of missing arguments", null) + return + } + + val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager + if (clipboard != null) { + val clip = ClipData.newUri(context.contentResolver, label, getShareableUri(uri)) + clipboard.setPrimaryClip(clip) + result.success(true) + } else { + result.success(false) + } + } + private fun edit(title: String?, uri: Uri?, mimeType: String?): Boolean { uri ?: return false diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index fb3bcdedf..5a29e6150 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -62,6 +62,8 @@ "chipActionCreateAlbum": "Create album", "@chipActionCreateAlbum": {}, + "entryActionCopyToClipboard": "Copy to clipboard", + "@entryActionCopyToClipboard": {}, "entryActionDelete": "Delete", "@entryActionDelete": {}, "entryActionExport": "Export", diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index 7b2064a63..48d2c694f 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -31,6 +31,7 @@ "chipActionSetCover": "대표 이미지 변경", "chipActionCreateAlbum": "앨범 만들기", + "entryActionCopyToClipboard": "클립보드에 복사", "entryActionDelete": "삭제", "entryActionExport": "내보내기", "entryActionInfo": "상세정보", diff --git a/lib/model/actions/entry_actions.dart b/lib/model/actions/entry_actions.dart index b8e90c626..f54eecf70 100644 --- a/lib/model/actions/entry_actions.dart +++ b/lib/model/actions/entry_actions.dart @@ -19,6 +19,7 @@ enum EntryAction { // motion photo, viewMotionPhotoVideo, // external + copyToClipboard, edit, open, openMap, @@ -42,6 +43,7 @@ class EntryActions { EntryAction.delete, EntryAction.rename, EntryAction.export, + EntryAction.copyToClipboard, EntryAction.print, EntryAction.viewSource, EntryAction.viewMotionPhotoVideo, @@ -68,6 +70,8 @@ extension ExtraEntryAction on EntryAction { case EntryAction.toggleFavourite: // different data depending on toggle state return context.l10n.entryActionAddFavourite; + case EntryAction.copyToClipboard: + return context.l10n.entryActionCopyToClipboard; case EntryAction.delete: return context.l10n.entryActionDelete; case EntryAction.export: @@ -116,6 +120,8 @@ extension ExtraEntryAction on EntryAction { case EntryAction.toggleFavourite: // different data depending on toggle state return AIcons.favourite; + case EntryAction.copyToClipboard: + return AIcons.clipboard; case EntryAction.delete: return AIcons.delete; case EntryAction.export: diff --git a/lib/services/android_app_service.dart b/lib/services/android_app_service.dart index 4b1f50d2e..17c89058e 100644 --- a/lib/services/android_app_service.dart +++ b/lib/services/android_app_service.dart @@ -38,6 +38,19 @@ class AndroidAppService { return Uint8List(0); } + static Future copyToClipboard(String uri, String? label) async { + try { + final result = await platform.invokeMethod('copyToClipboard', { + 'uri': uri, + 'label': label, + }); + if (result != null) return result as bool; + } on PlatformException catch (e) { + debugPrint('copyToClipboard failed with code=${e.code}, exception=${e.message}, details=${e.details}'); + } + return false; + } + static Future edit(String uri, String mimeType) async { try { final result = await platform.invokeMethod('edit', { diff --git a/lib/theme/icons.dart b/lib/theme/icons.dart index 423dd0d92..ff4756a65 100644 --- a/lib/theme/icons.dart +++ b/lib/theme/icons.dart @@ -36,6 +36,7 @@ class AIcons { static const IconData skip10 = Icons.forward_10_outlined; static const IconData captureFrame = Icons.screenshot_outlined; static const IconData clear = Icons.clear_outlined; + static const IconData clipboard = Icons.content_copy_outlined; static const IconData createAlbum = Icons.add_circle_outline; static const IconData debug = Icons.whatshot_outlined; static const IconData delete = Icons.delete_outlined; diff --git a/lib/widgets/settings/viewer/viewer_actions_editor.dart b/lib/widgets/settings/viewer/viewer_actions_editor.dart index 33eebc9ce..f0b048731 100644 --- a/lib/widgets/settings/viewer/viewer_actions_editor.dart +++ b/lib/widgets/settings/viewer/viewer_actions_editor.dart @@ -36,6 +36,7 @@ class ViewerActionEditorPage extends StatelessWidget { EntryAction.delete, EntryAction.rename, EntryAction.export, + EntryAction.copyToClipboard, EntryAction.print, EntryAction.rotateScreen, EntryAction.flip, diff --git a/lib/widgets/viewer/entry_action_delegate.dart b/lib/widgets/viewer/entry_action_delegate.dart index c981424a3..c62bc91a6 100644 --- a/lib/widgets/viewer/entry_action_delegate.dart +++ b/lib/widgets/viewer/entry_action_delegate.dart @@ -37,6 +37,11 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix case EntryAction.toggleFavourite: entry.toggleFavourite(); break; + case EntryAction.copyToClipboard: + AndroidAppService.copyToClipboard(entry.uri, entry.bestTitle).then((success) { + showFeedback(context, success ? context.l10n.genericSuccessFeedback : context.l10n.genericFailureFeedback); + }); + break; case EntryAction.delete: _showDeleteDialog(context, entry); break; @@ -46,12 +51,12 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix case EntryAction.info: ShowInfoNotification().dispatch(context); break; - case EntryAction.rename: - _showRenameDialog(context, entry); - break; case EntryAction.print: EntryPrinter(entry).print(context); break; + case EntryAction.rename: + _showRenameDialog(context, entry); + break; case EntryAction.rotateCCW: _rotate(context, entry, clockwise: false); break; diff --git a/lib/widgets/viewer/overlay/top.dart b/lib/widgets/viewer/overlay/top.dart index 725998922..ba720cc13 100644 --- a/lib/widgets/viewer/overlay/top.dart +++ b/lib/widgets/viewer/overlay/top.dart @@ -86,11 +86,12 @@ class ViewerTopOverlay extends StatelessWidget { return targetEntry.isMotionPhoto; case EntryAction.rotateScreen: return settings.isRotationLocked; - case EntryAction.share: + case EntryAction.copyToClipboard: + case EntryAction.edit: case EntryAction.info: case EntryAction.open: - case EntryAction.edit: case EntryAction.setAs: + case EntryAction.share: return true; case EntryAction.debug: return kDebugMode; @@ -197,6 +198,7 @@ class _TopOverlayRow extends StatelessWidget { onPressed: onPressed, ); break; + case EntryAction.copyToClipboard: case EntryAction.delete: case EntryAction.export: case EntryAction.flip: