handle pick intent
This commit is contained in:
parent
e2cb03909a
commit
fb7df6fcf2
11 changed files with 133 additions and 64 deletions
|
@ -55,11 +55,22 @@
|
|||
<intent-filter tools:ignore="AppLinkUrlError">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="image/*" />
|
||||
<data android:mimeType="video/*" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.GET_CONTENT" />
|
||||
<category android:name="android.intent.category.OPENABLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="image/*" />
|
||||
<data android:mimeType="video/*" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.PICK" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="image/*" />
|
||||
<data android:mimeType="video/*" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
|
||||
android:value="false" />
|
||||
|
|
|
@ -32,7 +32,7 @@ public class MainActivity extends FlutterActivity {
|
|||
|
||||
public static final String VIEWER_CHANNEL = "deckers.thibault/aves/viewer";
|
||||
|
||||
private Map<String, String> sharedEntryMap;
|
||||
private Map<String, String> 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())) {
|
||||
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) {
|
||||
sharedEntryMap = new HashMap<>();
|
||||
sharedEntryMap.put("uri", uri.toString());
|
||||
sharedEntryMap.put("mimeType", mimeType);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<HomePage> {
|
||||
MediaStoreSource _mediaStore;
|
||||
ImageEntry _sharedEntry;
|
||||
ImageEntry _viewerEntry;
|
||||
Future<void> _appSetup;
|
||||
|
||||
@override
|
||||
|
@ -87,18 +91,36 @@ class _HomePageState extends State<HomePage> {
|
|||
|
||||
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<void> _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<HomePage> {
|
|||
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(
|
||||
|
|
|
@ -37,7 +37,7 @@ class AlbumFilter extends CollectionFilter {
|
|||
Future<Color> 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(
|
||||
|
|
|
@ -4,13 +4,23 @@ import 'package:flutter/services.dart';
|
|||
class ViewerService {
|
||||
static const platform = MethodChannel('deckers.thibault/aves/viewer');
|
||||
|
||||
static Future<Map> getSharedEntry() async {
|
||||
static Future<Map> 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<void> pick(String uri) async {
|
||||
try {
|
||||
await platform.invokeMethod('pick', <String, dynamic>{
|
||||
'uri': uri,
|
||||
});
|
||||
} on PlatformException catch (e) {
|
||||
debugPrint('pick failed with code=${e.code}, exception=${e.message}, details=${e.details}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<CollectionAppBar> 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<CollectionAppBar> 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,6 +171,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
..._buildSortMenuItems(),
|
||||
..._buildGroupMenuItems(),
|
||||
if (collection.isBrowsing) ...[
|
||||
if (AvesApp.mode == AppMode.main)
|
||||
const PopupMenuItem(
|
||||
value: CollectionAction.select,
|
||||
child: MenuRow(text: 'Select', icon: AIcons.select),
|
||||
|
|
|
@ -185,7 +185,7 @@ class _CollectionDrawerState extends State<CollectionDrawer> {
|
|||
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<CollectionDrawer> {
|
|||
final regularAlbums = <String>[], appAlbums = <String>[];
|
||||
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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 (AvesApp.mode == AppMode.main) {
|
||||
if (collection.isBrowsing) {
|
||||
_goToFullscreen(context);
|
||||
} else {
|
||||
} 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);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue