shortcut to search page
This commit is contained in:
parent
9da57961fc
commit
af9edebf86
12 changed files with 389 additions and 68 deletions
|
@ -87,21 +87,21 @@ public class MainActivity extends FlutterActivity {
|
||||||
private void setupShortcuts() {
|
private void setupShortcuts() {
|
||||||
ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);
|
ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);
|
||||||
|
|
||||||
Intent searchIntent = new Intent(Intent.ACTION_MAIN, null, this, MainActivity.class);
|
// do not use 'route' as extra key, as the Flutter framework acts on it
|
||||||
searchIntent.putExtra("page", "search");
|
|
||||||
ShortcutInfo search = new ShortcutInfo.Builder(this, "search")
|
ShortcutInfo search = new ShortcutInfo.Builder(this, "search")
|
||||||
.setShortLabel(getString(R.string.search_shortcut_short_label))
|
.setShortLabel(getString(R.string.search_shortcut_short_label))
|
||||||
.setIcon(Icon.createWithResource(this, R.drawable.ic_outline_search))
|
.setIcon(Icon.createWithResource(this, R.drawable.ic_outline_search))
|
||||||
.setIntent(searchIntent)
|
.setIntent(new Intent(Intent.ACTION_MAIN, null, this, MainActivity.class)
|
||||||
|
.putExtra("page", "/search"))
|
||||||
.build();
|
.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")
|
ShortcutInfo videos = new ShortcutInfo.Builder(this, "videos")
|
||||||
.setShortLabel(getString(R.string.videos_shortcut_short_label))
|
.setShortLabel(getString(R.string.videos_shortcut_short_label))
|
||||||
.setIcon(Icon.createWithResource(this, R.drawable.ic_outline_movie))
|
.setIcon(Icon.createWithResource(this, R.drawable.ic_outline_movie))
|
||||||
.setIntent(videosIntent)
|
.setIntent(new Intent(Intent.ACTION_MAIN, null, this, MainActivity.class)
|
||||||
|
.putExtra("page", "/collection")
|
||||||
|
.putExtra("filters", new String[]{"anyVideo"}))
|
||||||
.build();
|
.build();
|
||||||
shortcutManager.setDynamicShortcuts(Arrays.asList(videos, search));
|
shortcutManager.setDynamicShortcuts(Arrays.asList(videos, search));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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, search }
|
enum HomePageSetting { collection, albums }
|
||||||
|
|
||||||
extension ExtraHomePageSetting on HomePageSetting {
|
extension ExtraHomePageSetting on HomePageSetting {
|
||||||
String get name {
|
String get name {
|
||||||
|
@ -10,8 +10,6 @@ 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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -295,7 +295,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
collection.clearSelection();
|
collection.clearSelection();
|
||||||
break;
|
break;
|
||||||
case CollectionAction.stats:
|
case CollectionAction.stats:
|
||||||
unawaited(_goToStats());
|
_goToStats();
|
||||||
break;
|
break;
|
||||||
case CollectionAction.group:
|
case CollectionAction.group:
|
||||||
final value = await showDialog<EntryGroupFactor>(
|
final value = await showDialog<EntryGroupFactor>(
|
||||||
|
@ -338,14 +338,18 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
}
|
}
|
||||||
|
|
||||||
void _goToSearch() {
|
void _goToSearch() {
|
||||||
showSearch(
|
Navigator.push(
|
||||||
context: context,
|
context,
|
||||||
delegate: ImageSearchDelegate(collection.source, collection.addFilter),
|
SearchPageRoute(
|
||||||
);
|
delegate: ImageSearchDelegate(
|
||||||
|
source: collection.source,
|
||||||
|
parentCollection: collection,
|
||||||
|
),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _goToStats() {
|
void _goToStats() {
|
||||||
return Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
settings: RouteSettings(name: StatsPage.routeName),
|
settings: RouteSettings(name: StatsPage.routeName),
|
||||||
|
|
|
@ -6,40 +6,46 @@ import 'package:aves/model/filters/mime.dart';
|
||||||
import 'package:aves/model/filters/query.dart';
|
import 'package:aves/model/filters/query.dart';
|
||||||
import 'package:aves/model/filters/tag.dart';
|
import 'package:aves/model/filters/tag.dart';
|
||||||
import 'package:aves/model/mime_types.dart';
|
import 'package:aves/model/mime_types.dart';
|
||||||
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/album.dart';
|
import 'package:aves/model/source/album.dart';
|
||||||
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/source/location.dart';
|
import 'package:aves/model/source/location.dart';
|
||||||
import 'package:aves/model/source/tag.dart';
|
import 'package:aves/model/source/tag.dart';
|
||||||
|
import 'package:aves/widgets/collection/collection_page.dart';
|
||||||
import 'package:aves/widgets/collection/search/expandable_filter_row.dart';
|
import 'package:aves/widgets/collection/search/expandable_filter_row.dart';
|
||||||
|
import 'package:aves/widgets/collection/search_page.dart';
|
||||||
import 'package:aves/widgets/common/aves_filter_chip.dart';
|
import 'package:aves/widgets/common/aves_filter_chip.dart';
|
||||||
import 'package:aves/widgets/common/icons.dart';
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
class ImageSearchDelegate extends SearchDelegate<CollectionFilter> {
|
class ImageSearchDelegate {
|
||||||
final CollectionSource source;
|
final CollectionSource source;
|
||||||
final ValueNotifier<String> expandedSectionNotifier = ValueNotifier(null);
|
final ValueNotifier<String> expandedSectionNotifier = ValueNotifier(null);
|
||||||
final FilterCallback onSelection;
|
final CollectionLens parentCollection;
|
||||||
|
|
||||||
ImageSearchDelegate(this.source, this.onSelection);
|
ImageSearchDelegate({@required this.source, this.parentCollection});
|
||||||
|
|
||||||
@override
|
|
||||||
ThemeData appBarTheme(BuildContext context) {
|
ThemeData appBarTheme(BuildContext context) {
|
||||||
return Theme.of(context);
|
return Theme.of(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildLeading(BuildContext context) {
|
Widget buildLeading(BuildContext context) {
|
||||||
return IconButton(
|
return Navigator.canPop(context)
|
||||||
|
? IconButton(
|
||||||
icon: AnimatedIcon(
|
icon: AnimatedIcon(
|
||||||
icon: AnimatedIcons.menu_arrow,
|
icon: AnimatedIcons.menu_arrow,
|
||||||
progress: transitionAnimation,
|
progress: transitionAnimation,
|
||||||
),
|
),
|
||||||
onPressed: () => _select(context, null),
|
onPressed: () => _goBack(context),
|
||||||
tooltip: 'Back',
|
tooltip: MaterialLocalizations.of(context).backButtonTooltip,
|
||||||
|
)
|
||||||
|
: CloseButton(
|
||||||
|
onPressed: SystemNavigator.pop,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
List<Widget> buildActions(BuildContext context) {
|
List<Widget> buildActions(BuildContext context) {
|
||||||
return [
|
return [
|
||||||
if (query.isNotEmpty)
|
if (query.isNotEmpty)
|
||||||
|
@ -54,7 +60,6 @@ class ImageSearchDelegate extends SearchDelegate<CollectionFilter> {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildSuggestions(BuildContext context) {
|
Widget buildSuggestions(BuildContext context) {
|
||||||
final upQuery = query.trim().toUpperCase();
|
final upQuery = query.trim().toUpperCase();
|
||||||
bool containQuery(String s) => s.toUpperCase().contains(upQuery);
|
bool containQuery(String s) => s.toUpperCase().contains(upQuery);
|
||||||
|
@ -137,7 +142,6 @@ class ImageSearchDelegate extends SearchDelegate<CollectionFilter> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildResults(BuildContext context) {
|
Widget buildResults(BuildContext context) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
// `buildResults` is called in the build phase,
|
// `buildResults` is called in the build phase,
|
||||||
|
@ -154,14 +158,160 @@ class ImageSearchDelegate extends SearchDelegate<CollectionFilter> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _select(BuildContext context, CollectionFilter filter) {
|
void _select(BuildContext context, CollectionFilter filter) {
|
||||||
|
if (parentCollection != null) {
|
||||||
|
_applyToParentCollectionPage(context, filter);
|
||||||
|
} else {
|
||||||
|
_goToCollectionPage(context, filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _applyToParentCollectionPage(BuildContext context, CollectionFilter filter) {
|
||||||
if (filter != null) {
|
if (filter != null) {
|
||||||
onSelection(filter);
|
parentCollection.addFilter(filter);
|
||||||
}
|
}
|
||||||
// we post closing the search page after applying the filter selection
|
// we post closing the search page after applying the filter selection
|
||||||
// so that hero animation target is ready in the `FilterBar`,
|
// so that hero animation target is ready in the `FilterBar`,
|
||||||
// even when the target is a child of an `AnimatedList`
|
// even when the target is a child of an `AnimatedList`
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
close(context, null);
|
_goBack(context);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _goBack(BuildContext context) {
|
||||||
|
_clean();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _goToCollectionPage(BuildContext context, CollectionFilter filter) {
|
||||||
|
_clean();
|
||||||
|
Navigator.pushAndRemoveUntil(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
settings: RouteSettings(name: CollectionPage.routeName),
|
||||||
|
builder: (context) => CollectionPage(CollectionLens(
|
||||||
|
source: source,
|
||||||
|
filters: [filter],
|
||||||
|
groupFactor: settings.collectionGroupFactor,
|
||||||
|
sortFactor: settings.collectionSortFactor,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
settings.navRemoveRoutePredicate(CollectionPage.routeName),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _clean() {
|
||||||
|
currentBody = null;
|
||||||
|
focusNode?.unfocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// adapted from `SearchDelegate`
|
||||||
|
|
||||||
|
void showResults(BuildContext context) {
|
||||||
|
focusNode?.unfocus();
|
||||||
|
currentBody = SearchBody.results;
|
||||||
|
}
|
||||||
|
|
||||||
|
void showSuggestions(BuildContext context) {
|
||||||
|
assert(focusNode != null, '_focusNode must be set by route before showSuggestions is called.');
|
||||||
|
focusNode.requestFocus();
|
||||||
|
currentBody = SearchBody.suggestions;
|
||||||
|
}
|
||||||
|
|
||||||
|
Animation<double> get transitionAnimation => proxyAnimation;
|
||||||
|
|
||||||
|
FocusNode focusNode;
|
||||||
|
|
||||||
|
final TextEditingController queryTextController = TextEditingController();
|
||||||
|
|
||||||
|
final ProxyAnimation proxyAnimation = ProxyAnimation(kAlwaysDismissedAnimation);
|
||||||
|
|
||||||
|
String get query => queryTextController.text;
|
||||||
|
|
||||||
|
set query(String value) {
|
||||||
|
assert(query != null);
|
||||||
|
queryTextController.text = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ValueNotifier<SearchBody> currentBodyNotifier = ValueNotifier<SearchBody>(null);
|
||||||
|
|
||||||
|
SearchBody get currentBody => currentBodyNotifier.value;
|
||||||
|
|
||||||
|
set currentBody(SearchBody value) {
|
||||||
|
currentBodyNotifier.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchPageRoute route;
|
||||||
|
}
|
||||||
|
|
||||||
|
// adapted from `SearchDelegate`
|
||||||
|
enum SearchBody { suggestions, results }
|
||||||
|
|
||||||
|
// adapted from `SearchDelegate`
|
||||||
|
class SearchPageRoute<T> extends PageRoute<T> {
|
||||||
|
SearchPageRoute({
|
||||||
|
@required this.delegate,
|
||||||
|
}) : assert(delegate != null),
|
||||||
|
super(settings: RouteSettings(name: SearchPage.routeName)) {
|
||||||
|
assert(
|
||||||
|
delegate.route == null,
|
||||||
|
'The ${delegate.runtimeType} instance is currently used by another active '
|
||||||
|
'search. Please close that search by calling close() on the SearchDelegate '
|
||||||
|
'before openening another search with the same delegate instance.',
|
||||||
|
);
|
||||||
|
delegate.route = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ImageSearchDelegate delegate;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color get barrierColor => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get barrierLabel => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Duration get transitionDuration => const Duration(milliseconds: 300);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get maintainState => false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildTransitions(
|
||||||
|
BuildContext context,
|
||||||
|
Animation<double> animation,
|
||||||
|
Animation<double> secondaryAnimation,
|
||||||
|
Widget child,
|
||||||
|
) {
|
||||||
|
return FadeTransition(
|
||||||
|
opacity: animation,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Animation<double> createAnimation() {
|
||||||
|
final animation = super.createAnimation();
|
||||||
|
delegate.proxyAnimation.parent = animation;
|
||||||
|
return animation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildPage(
|
||||||
|
BuildContext context,
|
||||||
|
Animation<double> animation,
|
||||||
|
Animation<double> secondaryAnimation,
|
||||||
|
) {
|
||||||
|
return SearchPage(
|
||||||
|
delegate: delegate,
|
||||||
|
animation: animation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didComplete(T result) {
|
||||||
|
super.didComplete(result);
|
||||||
|
assert(delegate.route == this);
|
||||||
|
delegate.route = null;
|
||||||
|
delegate.currentBody = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
127
lib/widgets/collection/search_page.dart
Normal file
127
lib/widgets/collection/search_page.dart
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
import 'package:aves/widgets/collection/search/search_delegate.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class SearchPage extends StatefulWidget {
|
||||||
|
static const routeName = '/search';
|
||||||
|
|
||||||
|
final ImageSearchDelegate delegate;
|
||||||
|
final Animation<double> animation;
|
||||||
|
|
||||||
|
const SearchPage({
|
||||||
|
this.delegate,
|
||||||
|
this.animation,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_SearchPageState createState() => _SearchPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SearchPageState extends State<SearchPage> {
|
||||||
|
FocusNode focusNode = FocusNode();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
widget.delegate.queryTextController.addListener(_onQueryChanged);
|
||||||
|
widget.animation.addStatusListener(_onAnimationStatusChanged);
|
||||||
|
widget.delegate.currentBodyNotifier.addListener(_onSearchBodyChanged);
|
||||||
|
focusNode.addListener(_onFocusChanged);
|
||||||
|
widget.delegate.focusNode = focusNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
widget.delegate.queryTextController.removeListener(_onQueryChanged);
|
||||||
|
widget.animation.removeStatusListener(_onAnimationStatusChanged);
|
||||||
|
widget.delegate.currentBodyNotifier.removeListener(_onSearchBodyChanged);
|
||||||
|
widget.delegate.focusNode = null;
|
||||||
|
focusNode.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onAnimationStatusChanged(AnimationStatus status) {
|
||||||
|
if (status != AnimationStatus.completed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
widget.animation.removeStatusListener(_onAnimationStatusChanged);
|
||||||
|
focusNode.requestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(SearchPage oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (widget.delegate != oldWidget.delegate) {
|
||||||
|
oldWidget.delegate.queryTextController.removeListener(_onQueryChanged);
|
||||||
|
widget.delegate.queryTextController.addListener(_onQueryChanged);
|
||||||
|
oldWidget.delegate.currentBodyNotifier.removeListener(_onSearchBodyChanged);
|
||||||
|
widget.delegate.currentBodyNotifier.addListener(_onSearchBodyChanged);
|
||||||
|
oldWidget.delegate.focusNode = null;
|
||||||
|
widget.delegate.focusNode = focusNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onFocusChanged() {
|
||||||
|
if (focusNode.hasFocus && widget.delegate.currentBody != SearchBody.suggestions) {
|
||||||
|
widget.delegate.showSuggestions(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onQueryChanged() {
|
||||||
|
setState(() {
|
||||||
|
// rebuild ourselves because query changed.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onSearchBodyChanged() {
|
||||||
|
setState(() {
|
||||||
|
// rebuild ourselves because search body changed.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = widget.delegate.appBarTheme(context);
|
||||||
|
Widget body;
|
||||||
|
switch (widget.delegate.currentBody) {
|
||||||
|
case SearchBody.suggestions:
|
||||||
|
body = KeyedSubtree(
|
||||||
|
key: ValueKey<SearchBody>(SearchBody.suggestions),
|
||||||
|
child: widget.delegate.buildSuggestions(context),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case SearchBody.results:
|
||||||
|
body = KeyedSubtree(
|
||||||
|
key: ValueKey<SearchBody>(SearchBody.results),
|
||||||
|
child: widget.delegate.buildResults(context),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: theme.primaryColor,
|
||||||
|
iconTheme: theme.primaryIconTheme,
|
||||||
|
textTheme: theme.primaryTextTheme,
|
||||||
|
brightness: theme.primaryColorBrightness,
|
||||||
|
leading: widget.delegate.buildLeading(context),
|
||||||
|
title: TextField(
|
||||||
|
controller: widget.delegate.queryTextController,
|
||||||
|
focusNode: focusNode,
|
||||||
|
style: theme.textTheme.headline6,
|
||||||
|
textInputAction: TextInputAction.search,
|
||||||
|
onSubmitted: (_) => widget.delegate.showResults(context),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
hintText: MaterialLocalizations.of(context).searchFieldLabel,
|
||||||
|
hintStyle: theme.inputDecorationTheme.hintStyle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: widget.delegate.buildActions(context),
|
||||||
|
),
|
||||||
|
body: AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
child: body,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,3 @@
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/services/android_app_service.dart';
|
import 'package:aves/services/android_app_service.dart';
|
||||||
|
@ -12,6 +10,7 @@ import 'package:aves/widgets/common/entry_actions.dart';
|
||||||
import 'package:aves/widgets/common/image_providers/uri_image_provider.dart';
|
import 'package:aves/widgets/common/image_providers/uri_image_provider.dart';
|
||||||
import 'package:aves/widgets/fullscreen/debug.dart';
|
import 'package:aves/widgets/fullscreen/debug.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:pdf/pdf.dart';
|
import 'package:pdf/pdf.dart';
|
||||||
import 'package:pdf/widgets.dart' as pdf;
|
import 'package:pdf/widgets.dart' as pdf;
|
||||||
|
@ -154,7 +153,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// leave viewer
|
// leave viewer
|
||||||
exit(0);
|
unawaited(SystemNavigator.pop());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import 'package:aves/widgets/common/aves_selection_dialog.dart';
|
||||||
import 'package:aves/widgets/common/icons.dart';
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
import 'package:aves/widgets/common/menu_row.dart';
|
import 'package:aves/widgets/common/menu_row.dart';
|
||||||
import 'package:aves/widgets/filter_grids/filter_grid_page.dart';
|
import 'package:aves/widgets/filter_grids/filter_grid_page.dart';
|
||||||
|
import 'package:aves/widgets/filter_grids/search_button.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
|
@ -36,7 +37,7 @@ class AlbumListPage extends StatelessWidget {
|
||||||
return FilterNavigationPage(
|
return FilterNavigationPage(
|
||||||
source: source,
|
source: source,
|
||||||
title: 'Albums',
|
title: 'Albums',
|
||||||
actions: _buildActions(),
|
actions: _buildActions(context),
|
||||||
filterEntries: getAlbumEntries(source),
|
filterEntries: getAlbumEntries(source),
|
||||||
filterBuilder: (s) => AlbumFilter(s, source.getUniqueAlbumName(s)),
|
filterBuilder: (s) => AlbumFilter(s, source.getUniqueAlbumName(s)),
|
||||||
emptyBuilder: () => EmptyContent(
|
emptyBuilder: () => EmptyContent(
|
||||||
|
@ -51,10 +52,10 @@ class AlbumListPage extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _buildActions() {
|
List<Widget> _buildActions(BuildContext context) {
|
||||||
return [
|
return [
|
||||||
Builder(
|
SearchButton(source),
|
||||||
builder: (context) => PopupMenuButton<ChipAction>(
|
PopupMenuButton<ChipAction>(
|
||||||
key: Key('appbar-menu-button'),
|
key: Key('appbar-menu-button'),
|
||||||
itemBuilder: (context) {
|
itemBuilder: (context) {
|
||||||
return [
|
return [
|
||||||
|
@ -67,7 +68,6 @@ class AlbumListPage extends StatelessWidget {
|
||||||
},
|
},
|
||||||
onSelected: (action) => _onChipActionSelected(context, action),
|
onSelected: (action) => _onChipActionSelected(context, action),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,12 @@ class DecoratedFilterChip extends StatelessWidget {
|
||||||
final FilterCallback onPressed;
|
final FilterCallback onPressed;
|
||||||
|
|
||||||
const DecoratedFilterChip({
|
const DecoratedFilterChip({
|
||||||
|
Key key,
|
||||||
@required this.source,
|
@required this.source,
|
||||||
@required this.filter,
|
@required this.filter,
|
||||||
@required this.entry,
|
@required this.entry,
|
||||||
@required this.onPressed,
|
@required this.onPressed,
|
||||||
});
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
|
@ -119,6 +119,7 @@ class FilterGridPage extends StatelessWidget {
|
||||||
(context, i) {
|
(context, i) {
|
||||||
final key = filterKeys[i];
|
final key = filterKeys[i];
|
||||||
final child = DecoratedFilterChip(
|
final child = DecoratedFilterChip(
|
||||||
|
key: Key(key),
|
||||||
source: source,
|
source: source,
|
||||||
filter: filterBuilder(key),
|
filter: filterBuilder(key),
|
||||||
entry: filterEntries[key],
|
entry: filterEntries[key],
|
||||||
|
|
29
lib/widgets/filter_grids/search_button.dart
Normal file
29
lib/widgets/filter_grids/search_button.dart
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
|
import 'package:aves/widgets/collection/search/search_delegate.dart';
|
||||||
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class SearchButton extends StatelessWidget {
|
||||||
|
final CollectionSource source;
|
||||||
|
|
||||||
|
const SearchButton(this.source);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return IconButton(
|
||||||
|
key: Key('search-button'),
|
||||||
|
icon: Icon(AIcons.search),
|
||||||
|
onPressed: () => _goToSearch(context),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _goToSearch(BuildContext context) {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
SearchPageRoute(
|
||||||
|
delegate: ImageSearchDelegate(
|
||||||
|
source: source,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
|
@ -325,11 +325,12 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onLeave() {
|
void _onLeave() {
|
||||||
if (!Navigator.canPop(context)) {
|
if (Navigator.canPop(context)) {
|
||||||
// exit app when trying to pop a fullscreen page that is a viewer for a single entry
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
_showSystemUI();
|
_showSystemUI();
|
||||||
|
} else {
|
||||||
|
// exit app when trying to pop a fullscreen page that is a viewer for a single entry
|
||||||
|
SystemNavigator.pop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// system UI
|
// system UI
|
||||||
|
|
|
@ -10,6 +10,8 @@ import 'package:aves/services/image_file_service.dart';
|
||||||
import 'package:aves/services/viewer_service.dart';
|
import 'package:aves/services/viewer_service.dart';
|
||||||
import 'package:aves/utils/android_file_utils.dart';
|
import 'package:aves/utils/android_file_utils.dart';
|
||||||
import 'package:aves/widgets/collection/collection_page.dart';
|
import 'package:aves/widgets/collection/collection_page.dart';
|
||||||
|
import 'package:aves/widgets/collection/search/search_delegate.dart';
|
||||||
|
import 'package:aves/widgets/collection/search_page.dart';
|
||||||
import 'package:aves/widgets/common/data_providers/media_store_collection_provider.dart';
|
import 'package:aves/widgets/common/data_providers/media_store_collection_provider.dart';
|
||||||
import 'package:aves/widgets/common/routes.dart';
|
import 'package:aves/widgets/common/routes.dart';
|
||||||
import 'package:aves/widgets/filter_grids/albums_page.dart';
|
import 'package:aves/widgets/filter_grids/albums_page.dart';
|
||||||
|
@ -32,7 +34,7 @@ class HomePage extends StatefulWidget {
|
||||||
class _HomePageState extends State<HomePage> {
|
class _HomePageState extends State<HomePage> {
|
||||||
MediaStoreSource _mediaStore;
|
MediaStoreSource _mediaStore;
|
||||||
ImageEntry _viewerEntry;
|
ImageEntry _viewerEntry;
|
||||||
HomePageSetting _shortcutPage;
|
String _shortcutRouteName;
|
||||||
List<String> _shortcutFilters;
|
List<String> _shortcutFilters;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -83,8 +85,14 @@ class _HomePageState extends State<HomePage> {
|
||||||
debugPrint('pick mimeType=$pickMimeTypes');
|
debugPrint('pick mimeType=$pickMimeTypes');
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
final extraPage = intentData['page'];
|
// do not use 'route' as extra key, as the Flutter framework acts on it
|
||||||
_shortcutPage = HomePageSetting.values.firstWhere((v) => v.toString().split('.')[1] == extraPage, orElse: () => null);
|
final extraRoute = intentData['page'];
|
||||||
|
switch (extraRoute) {
|
||||||
|
case CollectionPage.routeName:
|
||||||
|
case AlbumListPage.routeName:
|
||||||
|
case SearchPage.routeName:
|
||||||
|
_shortcutRouteName = extraRoute;
|
||||||
|
}
|
||||||
final extraFilters = intentData['filters'];
|
final extraFilters = intentData['filters'];
|
||||||
_shortcutFilters = extraFilters != null ? (extraFilters as List).cast<String>() : null;
|
_shortcutFilters = extraFilters != null ? (extraFilters as List).cast<String>() : null;
|
||||||
}
|
}
|
||||||
|
@ -117,12 +125,12 @@ class _HomePageState extends State<HomePage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
HomePageSetting startPage;
|
String routeName;
|
||||||
Iterable<CollectionFilter> filters;
|
Iterable<CollectionFilter> filters;
|
||||||
if (AvesApp.mode == AppMode.pick) {
|
if (AvesApp.mode == AppMode.pick) {
|
||||||
startPage = HomePageSetting.collection;
|
routeName = CollectionPage.routeName;
|
||||||
} else {
|
} else {
|
||||||
startPage = _shortcutPage ?? settings.homePage;
|
routeName = _shortcutRouteName ?? settings.homePage.routeName;
|
||||||
filters = (_shortcutFilters ?? []).map((filterString) {
|
filters = (_shortcutFilters ?? []).map((filterString) {
|
||||||
switch (filterString) {
|
switch (filterString) {
|
||||||
case 'anyVideo':
|
case 'anyVideo':
|
||||||
|
@ -132,14 +140,17 @@ class _HomePageState extends State<HomePage> {
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
switch (startPage) {
|
switch (routeName) {
|
||||||
case HomePageSetting.albums:
|
case AlbumListPage.routeName:
|
||||||
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 SearchPage.routeName:
|
||||||
case HomePageSetting.collection:
|
return SearchPageRoute(
|
||||||
|
delegate: ImageSearchDelegate(source: _mediaStore),
|
||||||
|
);
|
||||||
|
case CollectionPage.routeName:
|
||||||
default:
|
default:
|
||||||
return DirectMaterialPageRoute(
|
return DirectMaterialPageRoute(
|
||||||
settings: RouteSettings(name: CollectionPage.routeName),
|
settings: RouteSettings(name: CollectionPage.routeName),
|
||||||
|
|
Loading…
Reference in a new issue