From fb7df6fcf2b57b68c8f2dbcfa2f2f4f112d2f4a6 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Mon, 18 May 2020 18:02:46 +0900 Subject: [PATCH] handle pick intent --- android/app/src/main/AndroidManifest.xml | 17 +++++-- .../deckers/thibault/aves/MainActivity.java | 48 ++++++++++++++----- lib/main.dart | 42 ++++++++++++---- lib/model/filters/album.dart | 2 +- lib/services/viewer_service.dart | 18 +++++-- lib/utils/android_file_utils.dart | 21 +++----- lib/widgets/album/app_bar.dart | 13 +++-- lib/widgets/album/collection_drawer.dart | 6 +-- lib/widgets/album/grid/header_generic.dart | 2 +- lib/widgets/album/grid/list_sliver.dart | 16 +++++-- lib/widgets/common/icons.dart | 12 ++--- 11 files changed, 133 insertions(+), 64 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index ed7067fc6..dd5e3e29b 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -55,11 +55,22 @@ - - + + + + + + + + + + + + + @@ -77,7 +88,7 @@ - sharedEntryMap; + private Map intentDataMap; @Override protected void onCreate(Bundle savedInstanceState) { @@ -60,23 +60,47 @@ public class MainActivity extends FlutterActivity { new MethodChannel(messenger, VIEWER_CHANNEL).setMethodCallHandler( (call, result) -> { - if (call.method.contentEquals("getSharedEntry")) { - result.success(sharedEntryMap); - sharedEntryMap = null; + if (call.method.contentEquals("getIntentData")) { + result.success(intentDataMap); + intentDataMap = null; + } else if (call.method.contentEquals("pick")) { + result.success(intentDataMap); + intentDataMap = null; + String resultUri = call.argument("uri"); + if (resultUri != null) { + Intent data = new Intent(); + data.setData(Uri.parse(resultUri)); + setResult(RESULT_OK, data); + finish(); + } else { + setResult(RESULT_CANCELED); + finish(); + } } }); } private void handleIntent(Intent intent) { Log.i(LOG_TAG, "handleIntent intent=" + intent); - if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) { - Uri uri = intent.getData(); - String mimeType = intent.getType(); - if (uri != null && mimeType != null) { - sharedEntryMap = new HashMap<>(); - sharedEntryMap.put("uri", uri.toString()); - sharedEntryMap.put("mimeType", mimeType); - } + if (intent == null) return; + String action = intent.getAction(); + if (action == null) return; + switch (action) { + case Intent.ACTION_VIEW: + Uri uri = intent.getData(); + String mimeType = intent.getType(); + if (uri != null && mimeType != null) { + intentDataMap = new HashMap<>(); + intentDataMap.put("action", "view"); + intentDataMap.put("uri", uri.toString()); + intentDataMap.put("mimeType", mimeType); + } + break; + case Intent.ACTION_GET_CONTENT: + case Intent.ACTION_PICK: + intentDataMap = new HashMap<>(); + intentDataMap.put("action", "pick"); + break; } } diff --git a/lib/main.dart b/lib/main.dart index 4a1a963da..0ff76ab3d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -20,7 +20,11 @@ void main() { runApp(AvesApp()); } +enum AppMode { main, pick, view } + class AvesApp extends StatelessWidget { + static AppMode mode = AppMode.main; + @override Widget build(BuildContext context) { return MaterialApp( @@ -56,7 +60,7 @@ class HomePage extends StatefulWidget { class _HomePageState extends State { MediaStoreSource _mediaStore; - ImageEntry _sharedEntry; + ImageEntry _viewerEntry; Future _appSetup; @override @@ -87,18 +91,36 @@ class _HomePageState extends State { await settings.init(); // <20ms - final sharedExtra = await ViewerService.getSharedEntry(); - if (sharedExtra != null) { - _sharedEntry = await ImageFileService.getImageEntry(sharedExtra['uri'], sharedExtra['mimeType']); - // cataloging is essential for geolocation and video rotation - await _sharedEntry.catalog(); - unawaited(_sharedEntry.locate()); - } else { + final intentData = await ViewerService.getIntentData(); + if (intentData != null) { + final action = intentData['action']; + switch (action) { + case 'view': + AvesApp.mode = AppMode.view; + await _initViewerEntry( + uri: intentData['uri'], + mimeType: intentData['mimeType'], + ); + break; + case 'pick': + AvesApp.mode = AppMode.pick; + break; + } + } + + if (AvesApp.mode != AppMode.view) { _mediaStore = MediaStoreSource(); unawaited(_mediaStore.fetch()); } } + Future _initViewerEntry({@required String uri, @required String mimeType}) async { + _viewerEntry = await ImageFileService.getImageEntry(uri, mimeType); + // cataloging is essential for geolocation and video rotation + await _viewerEntry.catalog(); + unawaited(_viewerEntry.locate()); + } + @override Widget build(BuildContext context) { return FutureBuilder( @@ -107,8 +129,8 @@ class _HomePageState extends State { if (snapshot.hasError) return const Icon(AIcons.error); if (snapshot.connectionState != ConnectionState.done) return const SizedBox.shrink(); debugPrint('$runtimeType app setup future complete'); - if (_sharedEntry != null) { - return SingleFullscreenPage(entry: _sharedEntry); + if (AvesApp.mode == AppMode.view) { + return SingleFullscreenPage(entry: _viewerEntry); } if (_mediaStore != null) { return CollectionPage(CollectionLens( diff --git a/lib/model/filters/album.dart b/lib/model/filters/album.dart index 07d9a0ce5..c9a638fca 100644 --- a/lib/model/filters/album.dart +++ b/lib/model/filters/album.dart @@ -37,7 +37,7 @@ class AlbumFilter extends CollectionFilter { Future color(BuildContext context) { // do not use async/await and rely on `SynchronousFuture` // to prevent rebuilding of the `FutureBuilder` listening on this future - if (androidFileUtils.getAlbumType(album) == AlbumType.App) { + if (androidFileUtils.getAlbumType(album) == AlbumType.app) { if (_appColors.containsKey(album)) return SynchronousFuture(_appColors[album]); return PaletteGenerator.fromImageProvider( diff --git a/lib/services/viewer_service.dart b/lib/services/viewer_service.dart index cbaab3a26..175874e6d 100644 --- a/lib/services/viewer_service.dart +++ b/lib/services/viewer_service.dart @@ -4,13 +4,23 @@ import 'package:flutter/services.dart'; class ViewerService { static const platform = MethodChannel('deckers.thibault/aves/viewer'); - static Future getSharedEntry() async { + static Future getIntentData() async { try { - // return nullable map with: 'uri' 'mimeType' - return await platform.invokeMethod('getSharedEntry') as Map; + // return nullable map with 'action' and possibly 'uri' 'mimeType' + return await platform.invokeMethod('getIntentData') as Map; } on PlatformException catch (e) { - debugPrint('getSharedEntry failed with code=${e.code}, exception=${e.message}, details=${e.details}'); + debugPrint('getIntentData failed with code=${e.code}, exception=${e.message}, details=${e.details}'); } return {}; } + + static Future pick(String uri) async { + try { + await platform.invokeMethod('pick', { + 'uri': uri, + }); + } on PlatformException catch (e) { + debugPrint('pick failed with code=${e.code}, exception=${e.message}, details=${e.details}'); + } + } } diff --git a/lib/utils/android_file_utils.dart b/lib/utils/android_file_utils.dart index 8c94ea034..ceee8c493 100644 --- a/lib/utils/android_file_utils.dart +++ b/lib/utils/android_file_utils.dart @@ -38,15 +38,15 @@ class AndroidFileUtils { AlbumType getAlbumType(String albumDirectory) { if (albumDirectory != null) { - if (androidFileUtils.isCameraPath(albumDirectory)) return AlbumType.Camera; - if (androidFileUtils.isDownloadPath(albumDirectory)) return AlbumType.Download; - if (androidFileUtils.isScreenRecordingsPath(albumDirectory)) return AlbumType.ScreenRecordings; - if (androidFileUtils.isScreenshotsPath(albumDirectory)) return AlbumType.Screenshots; + if (androidFileUtils.isCameraPath(albumDirectory)) return AlbumType.camera; + if (androidFileUtils.isDownloadPath(albumDirectory)) return AlbumType.download; + if (androidFileUtils.isScreenRecordingsPath(albumDirectory)) return AlbumType.screenRecordings; + if (androidFileUtils.isScreenshotsPath(albumDirectory)) return AlbumType.screenshots; final parts = albumDirectory.split(separator); - if (albumDirectory.startsWith(androidFileUtils.externalStorage) && appNameMap.keys.contains(parts.last)) return AlbumType.App; + if (albumDirectory.startsWith(androidFileUtils.externalStorage) && appNameMap.keys.contains(parts.last)) return AlbumType.app; } - return AlbumType.Default; + return AlbumType.regular; } String getAlbumAppPackageName(String albumDirectory) { @@ -55,14 +55,7 @@ class AndroidFileUtils { } } -enum AlbumType { - Default, - App, - Camera, - Download, - ScreenRecordings, - Screenshots, -} +enum AlbumType { regular, app, camera, download, screenRecordings, screenshots } class StorageVolume { final String description, path, state; diff --git a/lib/widgets/album/app_bar.dart b/lib/widgets/album/app_bar.dart index 4df0e8abf..2473e8fd4 100644 --- a/lib/widgets/album/app_bar.dart +++ b/lib/widgets/album/app_bar.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:aves/main.dart'; import 'package:aves/model/collection_lens.dart'; import 'package:aves/model/settings.dart'; import 'package:aves/utils/constants.dart'; @@ -121,6 +122,7 @@ class _CollectionAppBarState extends State with SingleTickerPr Widget _buildAppBarTitle() { if (collection.isBrowsing) { + final title = AvesApp.mode == AppMode.pick ? 'Select' : 'Aves'; return GestureDetector( onTap: _goToSearch, // use a `Container` with a dummy color to make it expand @@ -130,7 +132,7 @@ class _CollectionAppBarState extends State with SingleTickerPr padding: const EdgeInsets.symmetric(horizontal: NavigationToolbar.kMiddleSpacing), color: Colors.transparent, height: kToolbarHeight, - child: const Text('Aves'), + child: Text(title), ), ); } else if (collection.isSelecting) { @@ -169,10 +171,11 @@ class _CollectionAppBarState extends State with SingleTickerPr ..._buildSortMenuItems(), ..._buildGroupMenuItems(), if (collection.isBrowsing) ...[ - const PopupMenuItem( - value: CollectionAction.select, - child: MenuRow(text: 'Select', icon: AIcons.select), - ), + if (AvesApp.mode == AppMode.main) + const PopupMenuItem( + value: CollectionAction.select, + child: MenuRow(text: 'Select', icon: AIcons.select), + ), const PopupMenuItem( value: CollectionAction.stats, child: MenuRow(text: 'Stats', icon: AIcons.stats), diff --git a/lib/widgets/album/collection_drawer.dart b/lib/widgets/album/collection_drawer.dart index f3fa25d25..dddc4eb8e 100644 --- a/lib/widgets/album/collection_drawer.dart +++ b/lib/widgets/album/collection_drawer.dart @@ -185,7 +185,7 @@ class _CollectionDrawerState extends State { builder: (context, snapshot) { final specialAlbums = source.sortedAlbums.where((album) { final type = androidFileUtils.getAlbumType(album); - return type != AlbumType.Default && type != AlbumType.App; + return type != AlbumType.regular && type != AlbumType.app; }); if (specialAlbums.isEmpty) return const SizedBox.shrink(); @@ -205,10 +205,10 @@ class _CollectionDrawerState extends State { final regularAlbums = [], appAlbums = []; for (var album in source.sortedAlbums) { switch (androidFileUtils.getAlbumType(album)) { - case AlbumType.Default: + case AlbumType.regular: regularAlbums.add(album); break; - case AlbumType.App: + case AlbumType.app: appAlbums.add(album); break; default: diff --git a/lib/widgets/album/grid/header_generic.dart b/lib/widgets/album/grid/header_generic.dart index 6fc27aa13..e876554f3 100644 --- a/lib/widgets/album/grid/header_generic.dart +++ b/lib/widgets/album/grid/header_generic.dart @@ -71,7 +71,7 @@ class SectionHeader extends StatelessWidget { var headerExtent = 0.0; if (sectionKey is String) { // only compute height for album headers, as they're the only likely ones to split on multiple lines - final hasLeading = androidFileUtils.getAlbumType(sectionKey) != AlbumType.Default; + final hasLeading = androidFileUtils.getAlbumType(sectionKey) != AlbumType.regular; final hasTrailing = androidFileUtils.isOnRemovableStorage(sectionKey); final text = source.getUniqueAlbumName(sectionKey); final maxWidth = scrollableWidth - TitleSectionHeader.padding.horizontal; diff --git a/lib/widgets/album/grid/list_sliver.dart b/lib/widgets/album/grid/list_sliver.dart index 275b80c22..ed565892e 100644 --- a/lib/widgets/album/grid/list_sliver.dart +++ b/lib/widgets/album/grid/list_sliver.dart @@ -1,5 +1,7 @@ +import 'package:aves/main.dart'; import 'package:aves/model/collection_lens.dart'; import 'package:aves/model/image_entry.dart'; +import 'package:aves/services/viewer_service.dart'; import 'package:aves/widgets/album/grid/list_known_extent.dart'; import 'package:aves/widgets/album/grid/list_section_layout.dart'; import 'package:aves/widgets/album/thumbnail/decorated.dart'; @@ -52,14 +54,18 @@ class GridThumbnail extends StatelessWidget { return GestureDetector( key: ValueKey(entry.uri), onTap: () { - if (collection.isBrowsing) { - _goToFullscreen(context); - } else { - collection.toggleSelection(entry); + if (AvesApp.mode == AppMode.main) { + if (collection.isBrowsing) { + _goToFullscreen(context); + } else if (collection.isSelecting) { + collection.toggleSelection(entry); + } + } else if (AvesApp.mode == AppMode.pick) { + ViewerService.pick(entry.uri); } }, onLongPress: () { - if (collection.isBrowsing) { + if (AvesApp.mode == AppMode.main && collection.isBrowsing) { collection.toggleSelection(entry); } }, diff --git a/lib/widgets/common/icons.dart b/lib/widgets/common/icons.dart index 619e14c31..c78f7dbb5 100644 --- a/lib/widgets/common/icons.dart +++ b/lib/widgets/common/icons.dart @@ -158,14 +158,14 @@ class IconUtils { double size = 24, }) { switch (androidFileUtils.getAlbumType(album)) { - case AlbumType.Camera: + case AlbumType.camera: return Icon(AIcons.cameraAlbum, size: size); - case AlbumType.Screenshots: - case AlbumType.ScreenRecordings: + case AlbumType.screenshots: + case AlbumType.screenRecordings: return Icon(AIcons.screenshotAlbum, size: size); - case AlbumType.Download: + case AlbumType.download: return Icon(AIcons.downloadAlbum, size: size); - case AlbumType.App: + case AlbumType.app: return Image( image: AppIconImage( packageName: androidFileUtils.getAlbumAppPackageName(album), @@ -174,7 +174,7 @@ class IconUtils { width: size, height: size, ); - case AlbumType.Default: + case AlbumType.regular: default: return null; }