app shortcuts (WIP)

This commit is contained in:
Thibault Deckers 2020-09-06 18:47:24 +09:00
parent 0c06bf8443
commit 9da57961fc
8 changed files with 121 additions and 35 deletions

View file

@ -45,19 +45,20 @@
<application <application
android:name="io.flutter.app.FlutterApplication" android:name="io.flutter.app.FlutterApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:requestLegacyExternalStorage="true"> android:requestLegacyExternalStorage="true">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/AppTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<meta-data <meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame" android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="false" /> android:value="false" />
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
@ -65,14 +66,17 @@
<intent-filter tools:ignore="AppLinkUrlError"> <intent-filter tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" /> <data android:mimeType="image/*" />
<data android:mimeType="video/*" /> <data android:mimeType="video/*" />
<data android:mimeType="vnd.android.cursor.dir/image" /> <data android:mimeType="vnd.android.cursor.dir/image" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.GET_CONTENT" /> <action android:name="android.intent.action.GET_CONTENT" />
<category android:name="android.intent.category.OPENABLE" /> <category android:name="android.intent.category.OPENABLE" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" /> <data android:mimeType="image/*" />
<data android:mimeType="video/*" /> <data android:mimeType="video/*" />
<data android:mimeType="vnd.android.cursor.dir/image" /> <data android:mimeType="vnd.android.cursor.dir/image" />
@ -80,6 +84,7 @@
<intent-filter> <intent-filter>
<action android:name="android.intent.action.PICK" /> <action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" /> <data android:mimeType="image/*" />
<data android:mimeType="video/*" /> <data android:mimeType="video/*" />
<data android:mimeType="vnd.android.cursor.dir/image" /> <data android:mimeType="vnd.android.cursor.dir/image" />

View file

@ -1,10 +1,18 @@
package deckers.thibault.aves; package deckers.thibault.aves;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.drawable.Icon;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
import androidx.annotation.RequiresApi;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -29,7 +37,7 @@ public class MainActivity extends FlutterActivity {
public static final String VIEWER_CHANNEL = "deckers.thibault/aves/viewer"; public static final String VIEWER_CHANNEL = "deckers.thibault/aves/viewer";
private Map<String, String> intentDataMap; private Map<String, Object> intentDataMap;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -69,6 +77,33 @@ public class MainActivity extends FlutterActivity {
finish(); finish();
} }
}); });
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
setupShortcuts();
}
}
@RequiresApi(Build.VERSION_CODES.N_MR1)
private void setupShortcuts() {
ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);
Intent searchIntent = new Intent(Intent.ACTION_MAIN, null, this, MainActivity.class);
searchIntent.putExtra("page", "search");
ShortcutInfo search = new ShortcutInfo.Builder(this, "search")
.setShortLabel(getString(R.string.search_shortcut_short_label))
.setIcon(Icon.createWithResource(this, R.drawable.ic_outline_search))
.setIntent(searchIntent)
.build();
Intent videosIntent = new Intent(Intent.ACTION_MAIN, null, this, MainActivity.class);
videosIntent.putExtra("page", "collection");
videosIntent.putExtra("filters", new String[]{"anyVideo"});
ShortcutInfo videos = new ShortcutInfo.Builder(this, "videos")
.setShortLabel(getString(R.string.videos_shortcut_short_label))
.setIcon(Icon.createWithResource(this, R.drawable.ic_outline_movie))
.setIntent(videosIntent)
.build();
shortcutManager.setDynamicShortcuts(Arrays.asList(videos, search));
} }
private void handleIntent(Intent intent) { private void handleIntent(Intent intent) {
@ -77,6 +112,15 @@ public class MainActivity extends FlutterActivity {
String action = intent.getAction(); String action = intent.getAction();
if (action == null) return; if (action == null) return;
switch (action) { switch (action) {
case Intent.ACTION_MAIN:
String page = intent.getStringExtra("page");
if (page != null) {
intentDataMap = new HashMap<>();
intentDataMap.put("page", page);
String[] filters = intent.getStringArrayExtra("filters");
intentDataMap.put("filters", filters != null ? new ArrayList<>(Arrays.asList(filters)) : null);
}
break;
case Intent.ACTION_VIEW: case Intent.ACTION_VIEW:
Uri uri = intent.getData(); Uri uri = intent.getData();
String mimeType = intent.getType(); String mimeType = intent.getType();

View file

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="?attr/colorControlNormal" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M4,6.47L5.76,10H20v8H4V6.47M22,4h-4l2,4h-3l-2,-4h-2l2,4h-3l-2,-4H8l2,4H7L5,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V4z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="?attr/colorControlNormal" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
</vector>

View file

@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">Aves</string> <string name="app_name">Aves</string>
<string name="search_shortcut_short_label">Search</string>
<string name="videos_shortcut_short_label">Videos</string>
</resources> </resources>

View file

@ -1,7 +1,7 @@
import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/collection/collection_page.dart';
import 'package:aves/widgets/filter_grids/albums_page.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart';
enum HomePageSetting { collection, albums } enum HomePageSetting { collection, albums, search }
extension ExtraHomePageSetting on HomePageSetting { extension ExtraHomePageSetting on HomePageSetting {
String get name { String get name {
@ -10,6 +10,8 @@ extension ExtraHomePageSetting on HomePageSetting {
return 'Collection'; return 'Collection';
case HomePageSetting.albums: case HomePageSetting.albums:
return 'Albums'; return 'Albums';
case HomePageSetting.search:
return 'Search';
default: default:
return toString(); return toString();
} }

View file

@ -135,7 +135,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
Widget _buildAppBarTitle() { Widget _buildAppBarTitle() {
if (collection.isBrowsing) { if (collection.isBrowsing) {
Widget title = Text( Widget title = Text(
AvesApp.mode == AppMode.pick ? 'Select' : 'Collection', AvesApp.mode == AppMode.pick ? 'Pick' : 'Collection',
key: Key('appbar-title'), key: Key('appbar-title'),
); );
if (AvesApp.mode == AppMode.main) { if (AvesApp.mode == AppMode.main) {

View file

@ -1,5 +1,8 @@
import 'package:aves/main.dart'; import 'package:aves/main.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/filters/mime.dart';
import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_entry.dart';
import 'package:aves/model/mime_types.dart';
import 'package:aves/model/settings/home_page.dart'; import 'package:aves/model/settings/home_page.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
@ -29,6 +32,8 @@ class HomePage extends StatefulWidget {
class _HomePageState extends State<HomePage> { class _HomePageState extends State<HomePage> {
MediaStoreSource _mediaStore; MediaStoreSource _mediaStore;
ImageEntry _viewerEntry; ImageEntry _viewerEntry;
HomePageSetting _shortcutPage;
List<String> _shortcutFilters;
@override @override
void initState() { void initState() {
@ -77,6 +82,11 @@ class _HomePageState extends State<HomePage> {
String pickMimeTypes = intentData['mimeType']; String pickMimeTypes = intentData['mimeType'];
debugPrint('pick mimeType=$pickMimeTypes'); debugPrint('pick mimeType=$pickMimeTypes');
break; break;
default:
final extraPage = intentData['page'];
_shortcutPage = HomePageSetting.values.firstWhere((v) => v.toString().split('.')[1] == extraPage, orElse: () => null);
final extraFilters = intentData['filters'];
_shortcutFilters = extraFilters != null ? (extraFilters as List).cast<String>() : null;
} }
} }
@ -100,27 +110,43 @@ class _HomePageState extends State<HomePage> {
} }
Route _getRedirectRoute() { Route _getRedirectRoute() {
switch (AvesApp.mode) { if (AvesApp.mode == AppMode.view) {
case AppMode.view:
return DirectMaterialPageRoute( return DirectMaterialPageRoute(
settings: RouteSettings(name: SingleFullscreenPage.routeName), settings: RouteSettings(name: SingleFullscreenPage.routeName),
builder: (_) => SingleFullscreenPage(entry: _viewerEntry), builder: (_) => SingleFullscreenPage(entry: _viewerEntry),
); );
case AppMode.main: }
case AppMode.pick:
if (_mediaStore != null) { HomePageSetting startPage;
switch (settings.homePage) { Iterable<CollectionFilter> filters;
if (AvesApp.mode == AppMode.pick) {
startPage = HomePageSetting.collection;
} else {
startPage = _shortcutPage ?? settings.homePage;
filters = (_shortcutFilters ?? []).map((filterString) {
switch (filterString) {
case 'anyVideo':
return MimeFilter(MimeTypes.anyVideo);
}
debugPrint('failed to parse shortcut filter=$filterString');
return null;
});
}
switch (startPage) {
case HomePageSetting.albums: case HomePageSetting.albums:
return DirectMaterialPageRoute( return DirectMaterialPageRoute(
settings: RouteSettings(name: AlbumListPage.routeName), settings: RouteSettings(name: AlbumListPage.routeName),
builder: (_) => AlbumListPage(source: _mediaStore), builder: (_) => AlbumListPage(source: _mediaStore),
); );
case HomePageSetting.search:
case HomePageSetting.collection: case HomePageSetting.collection:
default:
return DirectMaterialPageRoute( return DirectMaterialPageRoute(
settings: RouteSettings(name: CollectionPage.routeName), settings: RouteSettings(name: CollectionPage.routeName),
builder: (_) => CollectionPage( builder: (_) => CollectionPage(
CollectionLens( CollectionLens(
source: _mediaStore, source: _mediaStore,
filters: filters,
groupFactor: settings.collectionGroupFactor, groupFactor: settings.collectionGroupFactor,
sortFactor: settings.collectionSortFactor, sortFactor: settings.collectionSortFactor,
), ),
@ -129,6 +155,3 @@ class _HomePageState extends State<HomePage> {
} }
} }
} }
return null;
}
}