From 994acc81f8e9054f8e554b68ecd9e24677347f0b Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Sat, 5 Sep 2020 09:59:02 +0900 Subject: [PATCH 01/13] folder renaming --- lib/model/settings/home_page.dart | 2 +- lib/widgets/{album => collection}/app_bar.dart | 4 ++-- .../{album => collection}/collection_page.dart | 2 +- lib/widgets/{album => collection}/empty.dart | 0 lib/widgets/{album => collection}/filter_bar.dart | 0 .../{album => collection}/grid/header_album.dart | 2 +- .../{album => collection}/grid/header_date.dart | 2 +- .../{album => collection}/grid/header_generic.dart | 4 ++-- .../grid/list_known_extent.dart | 2 +- .../grid/list_section_layout.dart | 4 ++-- .../{album => collection}/grid/list_sliver.dart | 6 +++--- lib/widgets/{album => collection}/grid/scaling.dart | 8 ++++---- .../grid/tile_extent_manager.dart | 0 .../search/expandable_filter_row.dart | 0 .../search/search_delegate.dart | 2 +- .../{album => collection}/thumbnail/decorated.dart | 6 +++--- .../{album => collection}/thumbnail/overlay.dart | 0 .../{album => collection}/thumbnail/raster.dart | 0 .../{album => collection}/thumbnail/vector.dart | 0 .../{album => collection}/thumbnail_collection.dart | 12 ++++++------ .../action_delegates/selection_action_delegate.dart | 4 ++-- lib/widgets/drawer/collection_tile.dart | 2 +- lib/widgets/filter_grids/albums_page.dart | 2 +- lib/widgets/filter_grids/countries_page.dart | 2 +- lib/widgets/filter_grids/decorated_filter_chip.dart | 4 ++-- lib/widgets/filter_grids/filter_grid_page.dart | 2 +- lib/widgets/filter_grids/tags_page.dart | 2 +- lib/widgets/fullscreen/fullscreen_body.dart | 2 +- lib/widgets/fullscreen/image_view.dart | 2 +- lib/widgets/home_page.dart | 2 +- lib/widgets/stats/filter_table.dart | 2 +- lib/widgets/stats/stats.dart | 4 ++-- 32 files changed, 43 insertions(+), 43 deletions(-) rename lib/widgets/{album => collection}/app_bar.dart (98%) rename lib/widgets/{album => collection}/collection_page.dart (95%) rename lib/widgets/{album => collection}/empty.dart (100%) rename lib/widgets/{album => collection}/filter_bar.dart (100%) rename lib/widgets/{album => collection}/grid/header_album.dart (94%) rename lib/widgets/{album => collection}/grid/header_date.dart (96%) rename lib/widgets/{album => collection}/grid/header_generic.dart (98%) rename lib/widgets/{album => collection}/grid/list_known_extent.dart (99%) rename lib/widgets/{album => collection}/grid/list_section_layout.dart (97%) rename lib/widgets/{album => collection}/grid/list_sliver.dart (94%) rename lib/widgets/{album => collection}/grid/scaling.dart (97%) rename lib/widgets/{album => collection}/grid/tile_extent_manager.dart (100%) rename lib/widgets/{album => collection}/search/expandable_filter_row.dart (100%) rename lib/widgets/{album => collection}/search/search_delegate.dart (98%) rename lib/widgets/{album => collection}/thumbnail/decorated.dart (90%) rename lib/widgets/{album => collection}/thumbnail/overlay.dart (100%) rename lib/widgets/{album => collection}/thumbnail/raster.dart (100%) rename lib/widgets/{album => collection}/thumbnail/vector.dart (100%) rename lib/widgets/{album => collection}/thumbnail_collection.dart (95%) diff --git a/lib/model/settings/home_page.dart b/lib/model/settings/home_page.dart index 9cb131bbf..bc5c29b75 100644 --- a/lib/model/settings/home_page.dart +++ b/lib/model/settings/home_page.dart @@ -1,4 +1,4 @@ -import 'package:aves/widgets/album/collection_page.dart'; +import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart'; enum HomePageSetting { collection, albums } diff --git a/lib/widgets/album/app_bar.dart b/lib/widgets/collection/app_bar.dart similarity index 98% rename from lib/widgets/album/app_bar.dart rename to lib/widgets/collection/app_bar.dart index 9c9aa5d88..314f29ce5 100644 --- a/lib/widgets/album/app_bar.dart +++ b/lib/widgets/collection/app_bar.dart @@ -5,8 +5,8 @@ import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/enums.dart'; import 'package:aves/utils/durations.dart'; -import 'package:aves/widgets/album/filter_bar.dart'; -import 'package:aves/widgets/album/search/search_delegate.dart'; +import 'package:aves/widgets/collection/filter_bar.dart'; +import 'package:aves/widgets/collection/search/search_delegate.dart'; import 'package:aves/widgets/common/action_delegates/selection_action_delegate.dart'; import 'package:aves/widgets/common/app_bar_subtitle.dart'; import 'package:aves/widgets/common/aves_selection_dialog.dart'; diff --git a/lib/widgets/album/collection_page.dart b/lib/widgets/collection/collection_page.dart similarity index 95% rename from lib/widgets/album/collection_page.dart rename to lib/widgets/collection/collection_page.dart index 8be4a541e..35fdb5981 100644 --- a/lib/widgets/album/collection_page.dart +++ b/lib/widgets/collection/collection_page.dart @@ -1,5 +1,5 @@ import 'package:aves/model/source/collection_lens.dart'; -import 'package:aves/widgets/album/thumbnail_collection.dart'; +import 'package:aves/widgets/collection/thumbnail_collection.dart'; import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart'; import 'package:aves/widgets/common/double_back_pop.dart'; import 'package:aves/widgets/drawer/app_drawer.dart'; diff --git a/lib/widgets/album/empty.dart b/lib/widgets/collection/empty.dart similarity index 100% rename from lib/widgets/album/empty.dart rename to lib/widgets/collection/empty.dart diff --git a/lib/widgets/album/filter_bar.dart b/lib/widgets/collection/filter_bar.dart similarity index 100% rename from lib/widgets/album/filter_bar.dart rename to lib/widgets/collection/filter_bar.dart diff --git a/lib/widgets/album/grid/header_album.dart b/lib/widgets/collection/grid/header_album.dart similarity index 94% rename from lib/widgets/album/grid/header_album.dart rename to lib/widgets/collection/grid/header_album.dart index 249a016d4..5afd82fa3 100644 --- a/lib/widgets/album/grid/header_album.dart +++ b/lib/widgets/collection/grid/header_album.dart @@ -1,5 +1,5 @@ import 'package:aves/utils/android_file_utils.dart'; -import 'package:aves/widgets/album/grid/header_generic.dart'; +import 'package:aves/widgets/collection/grid/header_generic.dart'; import 'package:aves/widgets/common/icons.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/album/grid/header_date.dart b/lib/widgets/collection/grid/header_date.dart similarity index 96% rename from lib/widgets/album/grid/header_date.dart rename to lib/widgets/collection/grid/header_date.dart index 278b077dc..9e136c796 100644 --- a/lib/widgets/album/grid/header_date.dart +++ b/lib/widgets/collection/grid/header_date.dart @@ -1,5 +1,5 @@ import 'package:aves/utils/time_utils.dart'; -import 'package:aves/widgets/album/grid/header_generic.dart'; +import 'package:aves/widgets/collection/grid/header_generic.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; diff --git a/lib/widgets/album/grid/header_generic.dart b/lib/widgets/collection/grid/header_generic.dart similarity index 98% rename from lib/widgets/album/grid/header_generic.dart rename to lib/widgets/collection/grid/header_generic.dart index 1cf74d188..e267e2be4 100644 --- a/lib/widgets/album/grid/header_generic.dart +++ b/lib/widgets/collection/grid/header_generic.dart @@ -6,8 +6,8 @@ import 'package:aves/model/source/enums.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/utils/constants.dart'; import 'package:aves/utils/durations.dart'; -import 'package:aves/widgets/album/grid/header_album.dart'; -import 'package:aves/widgets/album/grid/header_date.dart'; +import 'package:aves/widgets/collection/grid/header_album.dart'; +import 'package:aves/widgets/collection/grid/header_date.dart'; import 'package:aves/widgets/common/icons.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; diff --git a/lib/widgets/album/grid/list_known_extent.dart b/lib/widgets/collection/grid/list_known_extent.dart similarity index 99% rename from lib/widgets/album/grid/list_known_extent.dart rename to lib/widgets/collection/grid/list_known_extent.dart index 020f4a61d..6ea65c1f8 100644 --- a/lib/widgets/album/grid/list_known_extent.dart +++ b/lib/widgets/collection/grid/list_known_extent.dart @@ -1,6 +1,6 @@ import 'dart:math' as math; -import 'package:aves/widgets/album/grid/list_section_layout.dart'; +import 'package:aves/widgets/collection/grid/list_section_layout.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; diff --git a/lib/widgets/album/grid/list_section_layout.dart b/lib/widgets/collection/grid/list_section_layout.dart similarity index 97% rename from lib/widgets/album/grid/list_section_layout.dart rename to lib/widgets/collection/grid/list_section_layout.dart index b2475c810..fe88bfd51 100644 --- a/lib/widgets/album/grid/list_section_layout.dart +++ b/lib/widgets/collection/grid/list_section_layout.dart @@ -2,8 +2,8 @@ import 'dart:math'; import 'package:aves/model/image_entry.dart'; import 'package:aves/model/source/collection_lens.dart'; -import 'package:aves/widgets/album/grid/header_generic.dart'; -import 'package:aves/widgets/album/grid/tile_extent_manager.dart'; +import 'package:aves/widgets/collection/grid/header_generic.dart'; +import 'package:aves/widgets/collection/grid/tile_extent_manager.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; diff --git a/lib/widgets/album/grid/list_sliver.dart b/lib/widgets/collection/grid/list_sliver.dart similarity index 94% rename from lib/widgets/album/grid/list_sliver.dart rename to lib/widgets/collection/grid/list_sliver.dart index 9e4006779..186df604d 100644 --- a/lib/widgets/album/grid/list_sliver.dart +++ b/lib/widgets/collection/grid/list_sliver.dart @@ -2,9 +2,9 @@ import 'package:aves/main.dart'; import 'package:aves/model/image_entry.dart'; import 'package:aves/model/source/collection_lens.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'; +import 'package:aves/widgets/collection/grid/list_known_extent.dart'; +import 'package:aves/widgets/collection/grid/list_section_layout.dart'; +import 'package:aves/widgets/collection/thumbnail/decorated.dart'; import 'package:aves/widgets/common/routes.dart'; import 'package:aves/widgets/fullscreen/fullscreen_page.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/album/grid/scaling.dart b/lib/widgets/collection/grid/scaling.dart similarity index 97% rename from lib/widgets/album/grid/scaling.dart rename to lib/widgets/collection/grid/scaling.dart index 0a6397fd7..b3887ba50 100644 --- a/lib/widgets/album/grid/scaling.dart +++ b/lib/widgets/collection/grid/scaling.dart @@ -3,10 +3,10 @@ import 'dart:ui' as ui; import 'package:aves/model/image_entry.dart'; import 'package:aves/utils/durations.dart'; -import 'package:aves/widgets/album/grid/list_section_layout.dart'; -import 'package:aves/widgets/album/grid/list_sliver.dart'; -import 'package:aves/widgets/album/grid/tile_extent_manager.dart'; -import 'package:aves/widgets/album/thumbnail/decorated.dart'; +import 'package:aves/widgets/collection/grid/list_section_layout.dart'; +import 'package:aves/widgets/collection/grid/list_sliver.dart'; +import 'package:aves/widgets/collection/grid/tile_extent_manager.dart'; +import 'package:aves/widgets/collection/thumbnail/decorated.dart'; import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; diff --git a/lib/widgets/album/grid/tile_extent_manager.dart b/lib/widgets/collection/grid/tile_extent_manager.dart similarity index 100% rename from lib/widgets/album/grid/tile_extent_manager.dart rename to lib/widgets/collection/grid/tile_extent_manager.dart diff --git a/lib/widgets/album/search/expandable_filter_row.dart b/lib/widgets/collection/search/expandable_filter_row.dart similarity index 100% rename from lib/widgets/album/search/expandable_filter_row.dart rename to lib/widgets/collection/search/expandable_filter_row.dart diff --git a/lib/widgets/album/search/search_delegate.dart b/lib/widgets/collection/search/search_delegate.dart similarity index 98% rename from lib/widgets/album/search/search_delegate.dart rename to lib/widgets/collection/search/search_delegate.dart index 28cc29f7a..ed570fc65 100644 --- a/lib/widgets/album/search/search_delegate.dart +++ b/lib/widgets/collection/search/search_delegate.dart @@ -10,7 +10,7 @@ import 'package:aves/model/source/album.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/location.dart'; import 'package:aves/model/source/tag.dart'; -import 'package:aves/widgets/album/search/expandable_filter_row.dart'; +import 'package:aves/widgets/collection/search/expandable_filter_row.dart'; import 'package:aves/widgets/common/aves_filter_chip.dart'; import 'package:aves/widgets/common/icons.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/album/thumbnail/decorated.dart b/lib/widgets/collection/thumbnail/decorated.dart similarity index 90% rename from lib/widgets/album/thumbnail/decorated.dart rename to lib/widgets/collection/thumbnail/decorated.dart index c283be3fe..738ba77ab 100644 --- a/lib/widgets/album/thumbnail/decorated.dart +++ b/lib/widgets/collection/thumbnail/decorated.dart @@ -1,8 +1,8 @@ import 'package:aves/model/image_entry.dart'; import 'package:aves/model/source/collection_lens.dart'; -import 'package:aves/widgets/album/thumbnail/overlay.dart'; -import 'package:aves/widgets/album/thumbnail/raster.dart'; -import 'package:aves/widgets/album/thumbnail/vector.dart'; +import 'package:aves/widgets/collection/thumbnail/overlay.dart'; +import 'package:aves/widgets/collection/thumbnail/raster.dart'; +import 'package:aves/widgets/collection/thumbnail/vector.dart'; import 'package:flutter/material.dart'; class DecoratedThumbnail extends StatelessWidget { diff --git a/lib/widgets/album/thumbnail/overlay.dart b/lib/widgets/collection/thumbnail/overlay.dart similarity index 100% rename from lib/widgets/album/thumbnail/overlay.dart rename to lib/widgets/collection/thumbnail/overlay.dart diff --git a/lib/widgets/album/thumbnail/raster.dart b/lib/widgets/collection/thumbnail/raster.dart similarity index 100% rename from lib/widgets/album/thumbnail/raster.dart rename to lib/widgets/collection/thumbnail/raster.dart diff --git a/lib/widgets/album/thumbnail/vector.dart b/lib/widgets/collection/thumbnail/vector.dart similarity index 100% rename from lib/widgets/album/thumbnail/vector.dart rename to lib/widgets/collection/thumbnail/vector.dart diff --git a/lib/widgets/album/thumbnail_collection.dart b/lib/widgets/collection/thumbnail_collection.dart similarity index 95% rename from lib/widgets/album/thumbnail_collection.dart rename to lib/widgets/collection/thumbnail_collection.dart index 5b79f008b..8d5328121 100644 --- a/lib/widgets/album/thumbnail_collection.dart +++ b/lib/widgets/collection/thumbnail_collection.dart @@ -6,12 +6,12 @@ import 'package:aves/model/mime_types.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/utils/durations.dart'; -import 'package:aves/widgets/album/app_bar.dart'; -import 'package:aves/widgets/album/empty.dart'; -import 'package:aves/widgets/album/grid/list_section_layout.dart'; -import 'package:aves/widgets/album/grid/list_sliver.dart'; -import 'package:aves/widgets/album/grid/scaling.dart'; -import 'package:aves/widgets/album/grid/tile_extent_manager.dart'; +import 'package:aves/widgets/collection/app_bar.dart'; +import 'package:aves/widgets/collection/empty.dart'; +import 'package:aves/widgets/collection/grid/list_section_layout.dart'; +import 'package:aves/widgets/collection/grid/list_sliver.dart'; +import 'package:aves/widgets/collection/grid/scaling.dart'; +import 'package:aves/widgets/collection/grid/tile_extent_manager.dart'; import 'package:aves/widgets/common/icons.dart'; import 'package:aves/widgets/common/scroll_thumb.dart'; import 'package:aves/widgets/common/sloppy_scroll_physics.dart'; diff --git a/lib/widgets/common/action_delegates/selection_action_delegate.dart b/lib/widgets/common/action_delegates/selection_action_delegate.dart index b58772ebe..d7a4b286e 100644 --- a/lib/widgets/common/action_delegates/selection_action_delegate.dart +++ b/lib/widgets/common/action_delegates/selection_action_delegate.dart @@ -9,8 +9,8 @@ import 'package:aves/model/source/collection_source.dart'; import 'package:aves/services/android_app_service.dart'; import 'package:aves/services/image_file_service.dart'; import 'package:aves/utils/durations.dart'; -import 'package:aves/widgets/album/app_bar.dart'; -import 'package:aves/widgets/album/empty.dart'; +import 'package:aves/widgets/collection/app_bar.dart'; +import 'package:aves/widgets/collection/empty.dart'; import 'package:aves/widgets/common/action_delegates/create_album_dialog.dart'; import 'package:aves/widgets/common/action_delegates/feedback.dart'; import 'package:aves/widgets/common/action_delegates/permission_aware.dart'; diff --git a/lib/widgets/drawer/collection_tile.dart b/lib/widgets/drawer/collection_tile.dart index 2acada773..9cff6347f 100644 --- a/lib/widgets/drawer/collection_tile.dart +++ b/lib/widgets/drawer/collection_tile.dart @@ -2,7 +2,7 @@ import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/widgets/album/collection_page.dart'; +import 'package:aves/widgets/collection/collection_page.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/filter_grids/albums_page.dart b/lib/widgets/filter_grids/albums_page.dart index c2b3d34b8..a95766ed1 100644 --- a/lib/widgets/filter_grids/albums_page.dart +++ b/lib/widgets/filter_grids/albums_page.dart @@ -6,7 +6,7 @@ import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/enums.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/utils/durations.dart'; -import 'package:aves/widgets/album/empty.dart'; +import 'package:aves/widgets/collection/empty.dart'; import 'package:aves/widgets/common/aves_selection_dialog.dart'; import 'package:aves/widgets/common/icons.dart'; import 'package:aves/widgets/common/menu_row.dart'; diff --git a/lib/widgets/filter_grids/countries_page.dart b/lib/widgets/filter_grids/countries_page.dart index c9098f568..d580f51d4 100644 --- a/lib/widgets/filter_grids/countries_page.dart +++ b/lib/widgets/filter_grids/countries_page.dart @@ -1,7 +1,7 @@ import 'package:aves/model/filters/location.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/location.dart'; -import 'package:aves/widgets/album/empty.dart'; +import 'package:aves/widgets/collection/empty.dart'; import 'package:aves/widgets/common/icons.dart'; import 'package:aves/widgets/filter_grids/filter_grid_page.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/filter_grids/decorated_filter_chip.dart b/lib/widgets/filter_grids/decorated_filter_chip.dart index 560889721..53f9d4e16 100644 --- a/lib/widgets/filter_grids/decorated_filter_chip.dart +++ b/lib/widgets/filter_grids/decorated_filter_chip.dart @@ -5,8 +5,8 @@ import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/image_entry.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/utils/android_file_utils.dart'; -import 'package:aves/widgets/album/thumbnail/raster.dart'; -import 'package:aves/widgets/album/thumbnail/vector.dart'; +import 'package:aves/widgets/collection/thumbnail/raster.dart'; +import 'package:aves/widgets/collection/thumbnail/vector.dart'; import 'package:aves/widgets/common/aves_filter_chip.dart'; import 'package:aves/widgets/common/icons.dart'; import 'package:aves/widgets/filter_grids/filter_grid_page.dart'; diff --git a/lib/widgets/filter_grids/filter_grid_page.dart b/lib/widgets/filter_grids/filter_grid_page.dart index 4c198d059..7f184b55a 100644 --- a/lib/widgets/filter_grids/filter_grid_page.dart +++ b/lib/widgets/filter_grids/filter_grid_page.dart @@ -6,7 +6,7 @@ import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/utils/durations.dart'; -import 'package:aves/widgets/album/collection_page.dart'; +import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/common/app_bar_subtitle.dart'; import 'package:aves/widgets/common/aves_filter_chip.dart'; import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart'; diff --git a/lib/widgets/filter_grids/tags_page.dart b/lib/widgets/filter_grids/tags_page.dart index 4b4693f89..be5bfd8f8 100644 --- a/lib/widgets/filter_grids/tags_page.dart +++ b/lib/widgets/filter_grids/tags_page.dart @@ -1,7 +1,7 @@ import 'package:aves/model/filters/tag.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/tag.dart'; -import 'package:aves/widgets/album/empty.dart'; +import 'package:aves/widgets/collection/empty.dart'; import 'package:aves/widgets/common/icons.dart'; import 'package:aves/widgets/filter_grids/filter_grid_page.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/fullscreen/fullscreen_body.dart b/lib/widgets/fullscreen/fullscreen_body.dart index 81e29e9d0..352902072 100644 --- a/lib/widgets/fullscreen/fullscreen_body.dart +++ b/lib/widgets/fullscreen/fullscreen_body.dart @@ -7,7 +7,7 @@ import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/utils/change_notifier.dart'; import 'package:aves/utils/durations.dart'; -import 'package:aves/widgets/album/collection_page.dart'; +import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/common/action_delegates/entry_action_delegate.dart'; import 'package:aves/widgets/common/image_providers/thumbnail_provider.dart'; import 'package:aves/widgets/common/image_providers/uri_image_provider.dart'; diff --git a/lib/widgets/fullscreen/image_view.dart b/lib/widgets/fullscreen/image_view.dart index 15b94d533..55e377e02 100644 --- a/lib/widgets/fullscreen/image_view.dart +++ b/lib/widgets/fullscreen/image_view.dart @@ -1,6 +1,6 @@ import 'package:aves/model/image_entry.dart'; import 'package:aves/model/settings/settings.dart'; -import 'package:aves/widgets/album/empty.dart'; +import 'package:aves/widgets/collection/empty.dart'; import 'package:aves/widgets/common/icons.dart'; import 'package:aves/widgets/common/image_providers/thumbnail_provider.dart'; import 'package:aves/widgets/common/image_providers/uri_image_provider.dart'; diff --git a/lib/widgets/home_page.dart b/lib/widgets/home_page.dart index a2181ee51..920c0b38d 100644 --- a/lib/widgets/home_page.dart +++ b/lib/widgets/home_page.dart @@ -6,7 +6,7 @@ import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/services/image_file_service.dart'; import 'package:aves/services/viewer_service.dart'; import 'package:aves/utils/android_file_utils.dart'; -import 'package:aves/widgets/album/collection_page.dart'; +import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/common/data_providers/media_store_collection_provider.dart'; import 'package:aves/widgets/common/routes.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart'; diff --git a/lib/widgets/stats/filter_table.dart b/lib/widgets/stats/filter_table.dart index ec3fb536c..ed946bae8 100644 --- a/lib/widgets/stats/filter_table.dart +++ b/lib/widgets/stats/filter_table.dart @@ -2,7 +2,7 @@ import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/utils/color_utils.dart'; -import 'package:aves/widgets/album/collection_page.dart'; +import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/common/aves_filter_chip.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/stats/stats.dart b/lib/widgets/stats/stats.dart index 2370e7261..ecaa1415b 100644 --- a/lib/widgets/stats/stats.dart +++ b/lib/widgets/stats/stats.dart @@ -9,8 +9,8 @@ import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/utils/color_utils.dart'; import 'package:aves/utils/constants.dart'; -import 'package:aves/widgets/album/collection_page.dart'; -import 'package:aves/widgets/album/empty.dart'; +import 'package:aves/widgets/collection/collection_page.dart'; +import 'package:aves/widgets/collection/empty.dart'; import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart'; import 'package:aves/widgets/common/icons.dart'; import 'package:aves/widgets/stats/filter_table.dart'; From 385a8ee4300ab6da2d2ba3ca19cc3ee6eac413d1 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Sat, 5 Sep 2020 10:10:09 +0900 Subject: [PATCH 02/13] drawer: fixed navigation stack when opening setttings/about/debug --- lib/widgets/drawer/app_drawer.dart | 3 +++ lib/widgets/drawer/tile.dart | 21 ++++++++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/lib/widgets/drawer/app_drawer.dart b/lib/widgets/drawer/app_drawer.dart index aaa1088ab..2f52c00a5 100644 --- a/lib/widgets/drawer/app_drawer.dart +++ b/lib/widgets/drawer/app_drawer.dart @@ -208,6 +208,7 @@ class _AppDrawerState extends State { Widget get settingsTile => NavTile( icon: AIcons.settings, title: 'Settings', + topLevel: false, routeName: SettingsPage.routeName, pageBuilder: (_) => SettingsPage(), ); @@ -215,6 +216,7 @@ class _AppDrawerState extends State { Widget get aboutTile => NavTile( icon: AIcons.info, title: 'About', + topLevel: false, routeName: AboutPage.routeName, pageBuilder: (_) => AboutPage(), ); @@ -222,6 +224,7 @@ class _AppDrawerState extends State { Widget get debugTile => NavTile( icon: AIcons.debug, title: 'Debug', + topLevel: false, routeName: DebugPage.routeName, pageBuilder: (_) => DebugPage(source: source), ); diff --git a/lib/widgets/drawer/tile.dart b/lib/widgets/drawer/tile.dart index 6ac50d08b..6b044cd98 100644 --- a/lib/widgets/drawer/tile.dart +++ b/lib/widgets/drawer/tile.dart @@ -9,6 +9,7 @@ class NavTile extends StatelessWidget { final IconData icon; final String title; final Widget trailing; + final bool topLevel; final String routeName; final WidgetBuilder pageBuilder; @@ -16,6 +17,7 @@ class NavTile extends StatelessWidget { @required this.icon, @required this.title, this.trailing, + this.topLevel = true, @required this.routeName, @required this.pageBuilder, }); @@ -42,14 +44,19 @@ class NavTile extends StatelessWidget { onTap: () { Navigator.pop(context); if (routeName != context.currentRouteName) { - Navigator.pushAndRemoveUntil( - context, - MaterialPageRoute( - settings: RouteSettings(name: routeName), - builder: pageBuilder, - ), - settings.navRemoveRoutePredicate(routeName), + final route = MaterialPageRoute( + settings: RouteSettings(name: routeName), + builder: pageBuilder, ); + if (topLevel) { + Navigator.pushAndRemoveUntil( + context, + route, + settings.navRemoveRoutePredicate(routeName), + ); + } else { + Navigator.push(context, route); + } } }, selected: context.currentRouteName == routeName, From c88b839960740419c173fc4e196abe3988da356b Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Sat, 5 Sep 2020 10:26:51 +0900 Subject: [PATCH 03/13] minor layout fix --- lib/widgets/common/aves_filter_chip.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/widgets/common/aves_filter_chip.dart b/lib/widgets/common/aves_filter_chip.dart index 6c7f402c1..783973e58 100644 --- a/lib/widgets/common/aves_filter_chip.dart +++ b/lib/widgets/common/aves_filter_chip.dart @@ -96,7 +96,7 @@ class _AvesFilterChipState extends State { mainAxisSize: MainAxisSize.min, children: [ content, - widget.details, + Flexible(child: widget.details), ], ); } From d0af2896acedc773a0100da6c5e921fb9a0a04ed Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Sat, 5 Sep 2020 10:36:01 +0900 Subject: [PATCH 04/13] viewer: fixed panning when zoomed in --- lib/widgets/fullscreen/image_page.dart | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/widgets/fullscreen/image_page.dart b/lib/widgets/fullscreen/image_page.dart index 49f7ecefe..82235c827 100644 --- a/lib/widgets/fullscreen/image_page.dart +++ b/lib/widgets/fullscreen/image_page.dart @@ -86,11 +86,14 @@ class SingleImagePageState extends State with AutomaticKeepAliv Widget build(BuildContext context) { super.build(context); - return ImageView( - entry: widget.entry, - onScaleChanged: widget.onScaleChanged, - onTap: widget.onTap, - videoControllers: widget.videoControllers, + return PhotoViewGestureDetectorScope( + axis: [Axis.vertical], + child: ImageView( + entry: widget.entry, + onScaleChanged: widget.onScaleChanged, + onTap: widget.onTap, + videoControllers: widget.videoControllers, + ), ); } From 0c06bf8443878d1325b877dc268593d9feec88ef Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Sun, 6 Sep 2020 18:41:58 +0900 Subject: [PATCH 05/13] changed dialog layout --- lib/widgets/common/aves_dialog.dart | 40 +++++++++++++++++------------ 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/lib/widgets/common/aves_dialog.dart b/lib/widgets/common/aves_dialog.dart index 2681b9101..e487b9b78 100644 --- a/lib/widgets/common/aves_dialog.dart +++ b/lib/widgets/common/aves_dialog.dart @@ -24,9 +24,16 @@ class AvesDialog extends AlertDialog { // to size itself to the content intrinsic size, // but the `ListView` viewport does not have one width: 1, - child: ListView( - shrinkWrap: true, - children: scrollableContent, + child: DecoratedBox( + decoration: BoxDecoration( + border: Border( + bottom: Divider.createBorderSide(context, width: 1), + ), + ), + child: ListView( + shrinkWrap: true, + children: scrollableContent, + ), ), ), ) @@ -47,20 +54,21 @@ class DialogTitle extends StatelessWidget { @override Widget build(BuildContext context) { - return Column( - children: [ - Padding( - padding: const EdgeInsets.symmetric(vertical: 20), - child: Text( - title, - style: TextStyle( - fontWeight: FontWeight.bold, - fontFamily: 'Concourse Caps', - ), - ), + return Container( + alignment: Alignment.center, + padding: const EdgeInsets.symmetric(vertical: 20), + decoration: BoxDecoration( + border: Border( + bottom: Divider.createBorderSide(context, width: 1), ), - Divider(height: 1), - ], + ), + child: Text( + title, + style: TextStyle( + fontWeight: FontWeight.bold, + fontFamily: 'Concourse Caps', + ), + ), ); } } From 9da57961fcc1ad11eac2efc3d4d3c9c1010c1a9b Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Sun, 6 Sep 2020 18:47:24 +0900 Subject: [PATCH 06/13] app shortcuts (WIP) --- android/app/src/main/AndroidManifest.xml | 11 ++- .../deckers/thibault/aves/MainActivity.java | 46 ++++++++++- .../main/res/drawable/ic_outline_movie.xml | 5 ++ .../main/res/drawable/ic_outline_search.xml | 5 ++ android/app/src/main/res/values/strings.xml | 2 + lib/model/settings/home_page.dart | 4 +- lib/widgets/collection/app_bar.dart | 2 +- lib/widgets/home_page.dart | 81 ++++++++++++------- 8 files changed, 121 insertions(+), 35 deletions(-) create mode 100644 android/app/src/main/res/drawable/ic_outline_movie.xml create mode 100644 android/app/src/main/res/drawable/ic_outline_search.xml diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index f1c539b32..c1717c39a 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -45,19 +45,20 @@ + @@ -65,14 +66,17 @@ + + + @@ -80,6 +84,7 @@ + diff --git a/android/app/src/main/java/deckers/thibault/aves/MainActivity.java b/android/app/src/main/java/deckers/thibault/aves/MainActivity.java index f248ce1cf..c5df45489 100644 --- a/android/app/src/main/java/deckers/thibault/aves/MainActivity.java +++ b/android/app/src/main/java/deckers/thibault/aves/MainActivity.java @@ -1,10 +1,18 @@ package deckers.thibault.aves; 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.os.Build; import android.os.Bundle; import android.util.Log; +import androidx.annotation.RequiresApi; + +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -29,7 +37,7 @@ public class MainActivity extends FlutterActivity { public static final String VIEWER_CHANNEL = "deckers.thibault/aves/viewer"; - private Map intentDataMap; + private Map intentDataMap; @Override protected void onCreate(Bundle savedInstanceState) { @@ -69,6 +77,33 @@ public class MainActivity extends FlutterActivity { 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) { @@ -77,6 +112,15 @@ public class MainActivity extends FlutterActivity { String action = intent.getAction(); if (action == null) return; 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: Uri uri = intent.getData(); String mimeType = intent.getType(); diff --git a/android/app/src/main/res/drawable/ic_outline_movie.xml b/android/app/src/main/res/drawable/ic_outline_movie.xml new file mode 100644 index 000000000..ca2e076e1 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_outline_movie.xml @@ -0,0 +1,5 @@ + + + diff --git a/android/app/src/main/res/drawable/ic_outline_search.xml b/android/app/src/main/res/drawable/ic_outline_search.xml new file mode 100644 index 000000000..029cb754c --- /dev/null +++ b/android/app/src/main/res/drawable/ic_outline_search.xml @@ -0,0 +1,5 @@ + + + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 53cda64da..c296a8709 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1,4 +1,6 @@ Aves + Search + Videos \ No newline at end of file diff --git a/lib/model/settings/home_page.dart b/lib/model/settings/home_page.dart index bc5c29b75..480c4e896 100644 --- a/lib/model/settings/home_page.dart +++ b/lib/model/settings/home_page.dart @@ -1,7 +1,7 @@ import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart'; -enum HomePageSetting { collection, albums } +enum HomePageSetting { collection, albums, search } extension ExtraHomePageSetting on HomePageSetting { String get name { @@ -10,6 +10,8 @@ extension ExtraHomePageSetting on HomePageSetting { return 'Collection'; case HomePageSetting.albums: return 'Albums'; + case HomePageSetting.search: + return 'Search'; default: return toString(); } diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart index 314f29ce5..8605d4636 100644 --- a/lib/widgets/collection/app_bar.dart +++ b/lib/widgets/collection/app_bar.dart @@ -135,7 +135,7 @@ class _CollectionAppBarState extends State with SingleTickerPr Widget _buildAppBarTitle() { if (collection.isBrowsing) { Widget title = Text( - AvesApp.mode == AppMode.pick ? 'Select' : 'Collection', + AvesApp.mode == AppMode.pick ? 'Pick' : 'Collection', key: Key('appbar-title'), ); if (AvesApp.mode == AppMode.main) { diff --git a/lib/widgets/home_page.dart b/lib/widgets/home_page.dart index 920c0b38d..936c64af4 100644 --- a/lib/widgets/home_page.dart +++ b/lib/widgets/home_page.dart @@ -1,5 +1,8 @@ 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/mime_types.dart'; import 'package:aves/model/settings/home_page.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; @@ -29,6 +32,8 @@ class HomePage extends StatefulWidget { class _HomePageState extends State { MediaStoreSource _mediaStore; ImageEntry _viewerEntry; + HomePageSetting _shortcutPage; + List _shortcutFilters; @override void initState() { @@ -77,6 +82,11 @@ class _HomePageState extends State { String pickMimeTypes = intentData['mimeType']; debugPrint('pick mimeType=$pickMimeTypes'); 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() : null; } } @@ -100,35 +110,48 @@ class _HomePageState extends State { } Route _getRedirectRoute() { - switch (AvesApp.mode) { - case AppMode.view: - return DirectMaterialPageRoute( - settings: RouteSettings(name: SingleFullscreenPage.routeName), - builder: (_) => SingleFullscreenPage(entry: _viewerEntry), - ); - case AppMode.main: - case AppMode.pick: - if (_mediaStore != null) { - switch (settings.homePage) { - case HomePageSetting.albums: - return DirectMaterialPageRoute( - settings: RouteSettings(name: AlbumListPage.routeName), - builder: (_) => AlbumListPage(source: _mediaStore), - ); - case HomePageSetting.collection: - return DirectMaterialPageRoute( - settings: RouteSettings(name: CollectionPage.routeName), - builder: (_) => CollectionPage( - CollectionLens( - source: _mediaStore, - groupFactor: settings.collectionGroupFactor, - sortFactor: settings.collectionSortFactor, - ), - ), - ); - } - } + if (AvesApp.mode == AppMode.view) { + return DirectMaterialPageRoute( + settings: RouteSettings(name: SingleFullscreenPage.routeName), + builder: (_) => SingleFullscreenPage(entry: _viewerEntry), + ); + } + + HomePageSetting startPage; + Iterable 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: + return DirectMaterialPageRoute( + settings: RouteSettings(name: AlbumListPage.routeName), + builder: (_) => AlbumListPage(source: _mediaStore), + ); + case HomePageSetting.search: + case HomePageSetting.collection: + default: + return DirectMaterialPageRoute( + settings: RouteSettings(name: CollectionPage.routeName), + builder: (_) => CollectionPage( + CollectionLens( + source: _mediaStore, + filters: filters, + groupFactor: settings.collectionGroupFactor, + sortFactor: settings.collectionSortFactor, + ), + ), + ); } - return null; } } From af9edebf864248f86a29d41c879b750f43b1fa42 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Mon, 7 Sep 2020 11:40:00 +0900 Subject: [PATCH 07/13] shortcut to search page --- .../deckers/thibault/aves/MainActivity.java | 14 +- lib/model/settings/home_page.dart | 4 +- lib/widgets/collection/app_bar.dart | 18 +- .../collection/search/search_delegate.dart | 186 ++++++++++++++++-- lib/widgets/collection/search_page.dart | 127 ++++++++++++ .../entry_action_delegate.dart | 5 +- lib/widgets/filter_grids/albums_page.dart | 32 +-- .../filter_grids/decorated_filter_chip.dart | 3 +- .../filter_grids/filter_grid_page.dart | 1 + lib/widgets/filter_grids/search_button.dart | 29 +++ lib/widgets/fullscreen/fullscreen_body.dart | 7 +- lib/widgets/home_page.dart | 31 ++- 12 files changed, 389 insertions(+), 68 deletions(-) create mode 100644 lib/widgets/collection/search_page.dart create mode 100644 lib/widgets/filter_grids/search_button.dart diff --git a/android/app/src/main/java/deckers/thibault/aves/MainActivity.java b/android/app/src/main/java/deckers/thibault/aves/MainActivity.java index c5df45489..58ae2c2ee 100644 --- a/android/app/src/main/java/deckers/thibault/aves/MainActivity.java +++ b/android/app/src/main/java/deckers/thibault/aves/MainActivity.java @@ -87,21 +87,21 @@ public class MainActivity extends FlutterActivity { private void setupShortcuts() { ShortcutManager shortcutManager = getSystemService(ShortcutManager.class); - Intent searchIntent = new Intent(Intent.ACTION_MAIN, null, this, MainActivity.class); - searchIntent.putExtra("page", "search"); + // do not use 'route' as extra key, as the Flutter framework acts on it + 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) + .setIntent(new Intent(Intent.ACTION_MAIN, null, this, MainActivity.class) + .putExtra("page", "/search")) .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) + .setIntent(new Intent(Intent.ACTION_MAIN, null, this, MainActivity.class) + .putExtra("page", "/collection") + .putExtra("filters", new String[]{"anyVideo"})) .build(); shortcutManager.setDynamicShortcuts(Arrays.asList(videos, search)); } diff --git a/lib/model/settings/home_page.dart b/lib/model/settings/home_page.dart index 480c4e896..bc5c29b75 100644 --- a/lib/model/settings/home_page.dart +++ b/lib/model/settings/home_page.dart @@ -1,7 +1,7 @@ import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart'; -enum HomePageSetting { collection, albums, search } +enum HomePageSetting { collection, albums } extension ExtraHomePageSetting on HomePageSetting { String get name { @@ -10,8 +10,6 @@ extension ExtraHomePageSetting on HomePageSetting { return 'Collection'; case HomePageSetting.albums: return 'Albums'; - case HomePageSetting.search: - return 'Search'; default: return toString(); } diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart index 8605d4636..a89e81e47 100644 --- a/lib/widgets/collection/app_bar.dart +++ b/lib/widgets/collection/app_bar.dart @@ -295,7 +295,7 @@ class _CollectionAppBarState extends State with SingleTickerPr collection.clearSelection(); break; case CollectionAction.stats: - unawaited(_goToStats()); + _goToStats(); break; case CollectionAction.group: final value = await showDialog( @@ -338,14 +338,18 @@ class _CollectionAppBarState extends State with SingleTickerPr } void _goToSearch() { - showSearch( - context: context, - delegate: ImageSearchDelegate(collection.source, collection.addFilter), - ); + Navigator.push( + context, + SearchPageRoute( + delegate: ImageSearchDelegate( + source: collection.source, + parentCollection: collection, + ), + )); } - Future _goToStats() { - return Navigator.push( + void _goToStats() { + Navigator.push( context, MaterialPageRoute( settings: RouteSettings(name: StatsPage.routeName), diff --git a/lib/widgets/collection/search/search_delegate.dart b/lib/widgets/collection/search/search_delegate.dart index ed570fc65..b63699f09 100644 --- a/lib/widgets/collection/search/search_delegate.dart +++ b/lib/widgets/collection/search/search_delegate.dart @@ -6,40 +6,46 @@ import 'package:aves/model/filters/mime.dart'; import 'package:aves/model/filters/query.dart'; import 'package:aves/model/filters/tag.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/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/location.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_page.dart'; import 'package:aves/widgets/common/aves_filter_chip.dart'; import 'package:aves/widgets/common/icons.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; -class ImageSearchDelegate extends SearchDelegate { +class ImageSearchDelegate { final CollectionSource source; final ValueNotifier 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) { return Theme.of(context); } - @override Widget buildLeading(BuildContext context) { - return IconButton( - icon: AnimatedIcon( - icon: AnimatedIcons.menu_arrow, - progress: transitionAnimation, - ), - onPressed: () => _select(context, null), - tooltip: 'Back', - ); + return Navigator.canPop(context) + ? IconButton( + icon: AnimatedIcon( + icon: AnimatedIcons.menu_arrow, + progress: transitionAnimation, + ), + onPressed: () => _goBack(context), + tooltip: MaterialLocalizations.of(context).backButtonTooltip, + ) + : CloseButton( + onPressed: SystemNavigator.pop, + ); } - @override List buildActions(BuildContext context) { return [ if (query.isNotEmpty) @@ -54,7 +60,6 @@ class ImageSearchDelegate extends SearchDelegate { ]; } - @override Widget buildSuggestions(BuildContext context) { final upQuery = query.trim().toUpperCase(); bool containQuery(String s) => s.toUpperCase().contains(upQuery); @@ -137,7 +142,6 @@ class ImageSearchDelegate extends SearchDelegate { ); } - @override Widget buildResults(BuildContext context) { WidgetsBinding.instance.addPostFrameCallback((_) { // `buildResults` is called in the build phase, @@ -154,14 +158,160 @@ class ImageSearchDelegate extends SearchDelegate { } 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) { - onSelection(filter); + parentCollection.addFilter(filter); } // we post closing the search page after applying the filter selection // so that hero animation target is ready in the `FilterBar`, // even when the target is a child of an `AnimatedList` 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 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 currentBodyNotifier = ValueNotifier(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 extends PageRoute { + 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 animation, + Animation secondaryAnimation, + Widget child, + ) { + return FadeTransition( + opacity: animation, + child: child, + ); + } + + @override + Animation createAnimation() { + final animation = super.createAnimation(); + delegate.proxyAnimation.parent = animation; + return animation; + } + + @override + Widget buildPage( + BuildContext context, + Animation animation, + Animation 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; + } } diff --git a/lib/widgets/collection/search_page.dart b/lib/widgets/collection/search_page.dart new file mode 100644 index 000000000..c131ef739 --- /dev/null +++ b/lib/widgets/collection/search_page.dart @@ -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 animation; + + const SearchPage({ + this.delegate, + this.animation, + }); + + @override + _SearchPageState createState() => _SearchPageState(); +} + +class _SearchPageState extends State { + 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.suggestions), + child: widget.delegate.buildSuggestions(context), + ); + break; + case SearchBody.results: + body = KeyedSubtree( + key: ValueKey(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, + ), + ); + } +} diff --git a/lib/widgets/common/action_delegates/entry_action_delegate.dart b/lib/widgets/common/action_delegates/entry_action_delegate.dart index 4aac59c72..d3b167365 100644 --- a/lib/widgets/common/action_delegates/entry_action_delegate.dart +++ b/lib/widgets/common/action_delegates/entry_action_delegate.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:aves/model/image_entry.dart'; import 'package:aves/model/source/collection_lens.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/fullscreen/debug.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:pdf/pdf.dart'; import 'package:pdf/widgets.dart' as pdf; @@ -154,7 +153,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin { } } else { // leave viewer - exit(0); + unawaited(SystemNavigator.pop()); } } diff --git a/lib/widgets/filter_grids/albums_page.dart b/lib/widgets/filter_grids/albums_page.dart index a95766ed1..dc6539b94 100644 --- a/lib/widgets/filter_grids/albums_page.dart +++ b/lib/widgets/filter_grids/albums_page.dart @@ -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/menu_row.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:flutter/material.dart'; import 'package:flutter/scheduler.dart'; @@ -36,7 +37,7 @@ class AlbumListPage extends StatelessWidget { return FilterNavigationPage( source: source, title: 'Albums', - actions: _buildActions(), + actions: _buildActions(context), filterEntries: getAlbumEntries(source), filterBuilder: (s) => AlbumFilter(s, source.getUniqueAlbumName(s)), emptyBuilder: () => EmptyContent( @@ -51,22 +52,21 @@ class AlbumListPage extends StatelessWidget { ); } - List _buildActions() { + List _buildActions(BuildContext context) { return [ - Builder( - builder: (context) => PopupMenuButton( - key: Key('appbar-menu-button'), - itemBuilder: (context) { - return [ - PopupMenuItem( - key: Key('menu-sort'), - value: ChipAction.sort, - child: MenuRow(text: 'Sort...', icon: AIcons.sort), - ), - ]; - }, - onSelected: (action) => _onChipActionSelected(context, action), - ), + SearchButton(source), + PopupMenuButton( + key: Key('appbar-menu-button'), + itemBuilder: (context) { + return [ + PopupMenuItem( + key: Key('menu-sort'), + value: ChipAction.sort, + child: MenuRow(text: 'Sort...', icon: AIcons.sort), + ), + ]; + }, + onSelected: (action) => _onChipActionSelected(context, action), ), ]; } diff --git a/lib/widgets/filter_grids/decorated_filter_chip.dart b/lib/widgets/filter_grids/decorated_filter_chip.dart index 53f9d4e16..210311562 100644 --- a/lib/widgets/filter_grids/decorated_filter_chip.dart +++ b/lib/widgets/filter_grids/decorated_filter_chip.dart @@ -19,11 +19,12 @@ class DecoratedFilterChip extends StatelessWidget { final FilterCallback onPressed; const DecoratedFilterChip({ + Key key, @required this.source, @required this.filter, @required this.entry, @required this.onPressed, - }); + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/widgets/filter_grids/filter_grid_page.dart b/lib/widgets/filter_grids/filter_grid_page.dart index 7f184b55a..5daaa0473 100644 --- a/lib/widgets/filter_grids/filter_grid_page.dart +++ b/lib/widgets/filter_grids/filter_grid_page.dart @@ -119,6 +119,7 @@ class FilterGridPage extends StatelessWidget { (context, i) { final key = filterKeys[i]; final child = DecoratedFilterChip( + key: Key(key), source: source, filter: filterBuilder(key), entry: filterEntries[key], diff --git a/lib/widgets/filter_grids/search_button.dart b/lib/widgets/filter_grids/search_button.dart new file mode 100644 index 000000000..6a8577700 --- /dev/null +++ b/lib/widgets/filter_grids/search_button.dart @@ -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, + ), + )); + } +} diff --git a/lib/widgets/fullscreen/fullscreen_body.dart b/lib/widgets/fullscreen/fullscreen_body.dart index 352902072..8e3515585 100644 --- a/lib/widgets/fullscreen/fullscreen_body.dart +++ b/lib/widgets/fullscreen/fullscreen_body.dart @@ -325,11 +325,12 @@ class FullscreenBodyState extends State with SingleTickerProvide } void _onLeave() { - if (!Navigator.canPop(context)) { + if (Navigator.canPop(context)) { + _showSystemUI(); + } else { // exit app when trying to pop a fullscreen page that is a viewer for a single entry - exit(0); + SystemNavigator.pop(); } - _showSystemUI(); } // system UI diff --git a/lib/widgets/home_page.dart b/lib/widgets/home_page.dart index 936c64af4..b09537df5 100644 --- a/lib/widgets/home_page.dart +++ b/lib/widgets/home_page.dart @@ -10,6 +10,8 @@ import 'package:aves/services/image_file_service.dart'; import 'package:aves/services/viewer_service.dart'; import 'package:aves/utils/android_file_utils.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/routes.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart'; @@ -32,7 +34,7 @@ class HomePage extends StatefulWidget { class _HomePageState extends State { MediaStoreSource _mediaStore; ImageEntry _viewerEntry; - HomePageSetting _shortcutPage; + String _shortcutRouteName; List _shortcutFilters; @override @@ -83,8 +85,14 @@ class _HomePageState extends State { debugPrint('pick mimeType=$pickMimeTypes'); break; default: - final extraPage = intentData['page']; - _shortcutPage = HomePageSetting.values.firstWhere((v) => v.toString().split('.')[1] == extraPage, orElse: () => null); + // do not use 'route' as extra key, as the Flutter framework acts on it + final extraRoute = intentData['page']; + switch (extraRoute) { + case CollectionPage.routeName: + case AlbumListPage.routeName: + case SearchPage.routeName: + _shortcutRouteName = extraRoute; + } final extraFilters = intentData['filters']; _shortcutFilters = extraFilters != null ? (extraFilters as List).cast() : null; } @@ -117,12 +125,12 @@ class _HomePageState extends State { ); } - HomePageSetting startPage; + String routeName; Iterable filters; if (AvesApp.mode == AppMode.pick) { - startPage = HomePageSetting.collection; + routeName = CollectionPage.routeName; } else { - startPage = _shortcutPage ?? settings.homePage; + routeName = _shortcutRouteName ?? settings.homePage.routeName; filters = (_shortcutFilters ?? []).map((filterString) { switch (filterString) { case 'anyVideo': @@ -132,14 +140,17 @@ class _HomePageState extends State { return null; }); } - switch (startPage) { - case HomePageSetting.albums: + switch (routeName) { + case AlbumListPage.routeName: return DirectMaterialPageRoute( settings: RouteSettings(name: AlbumListPage.routeName), builder: (_) => AlbumListPage(source: _mediaStore), ); - case HomePageSetting.search: - case HomePageSetting.collection: + case SearchPage.routeName: + return SearchPageRoute( + delegate: ImageSearchDelegate(source: _mediaStore), + ); + case CollectionPage.routeName: default: return DirectMaterialPageRoute( settings: RouteSettings(name: CollectionPage.routeName), From 9fc6bd79a8fc746f437ee4de7dd8c85632335247 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Wed, 9 Sep 2020 12:08:25 +0900 Subject: [PATCH 08/13] filter serialization --- .../deckers/thibault/aves/MainActivity.java | 2 +- lib/model/filters/album.dart | 12 +++++++ lib/model/filters/favourite.dart | 4 +++ lib/model/filters/filters.dart | 23 ++++++++++++ lib/model/filters/location.dart | 15 +++++++- lib/model/filters/mime.dart | 10 ++++++ lib/model/filters/query.dart | 10 ++++++ lib/model/filters/tag.dart | 10 ++++++ lib/model/source/location.dart | 5 +-- .../fullscreen/info/location_section.dart | 2 +- lib/widgets/home_page.dart | 20 +++-------- lib/widgets/stats/stats.dart | 2 +- test/model/filters_test.dart | 35 +++++++++++++++++++ 13 files changed, 129 insertions(+), 21 deletions(-) create mode 100644 test/model/filters_test.dart diff --git a/android/app/src/main/java/deckers/thibault/aves/MainActivity.java b/android/app/src/main/java/deckers/thibault/aves/MainActivity.java index 58ae2c2ee..e4161da04 100644 --- a/android/app/src/main/java/deckers/thibault/aves/MainActivity.java +++ b/android/app/src/main/java/deckers/thibault/aves/MainActivity.java @@ -101,7 +101,7 @@ public class MainActivity extends FlutterActivity { .setIcon(Icon.createWithResource(this, R.drawable.ic_outline_movie)) .setIntent(new Intent(Intent.ACTION_MAIN, null, this, MainActivity.class) .putExtra("page", "/collection") - .putExtra("filters", new String[]{"anyVideo"})) + .putExtra("filters", new String[]{"{\"type\":\"mime\",\"mime\":\"video/*\"}"})) .build(); shortcutManager.setDynamicShortcuts(Arrays.asList(videos, search)); } diff --git a/lib/model/filters/album.dart b/lib/model/filters/album.dart index 6ad3a8be7..7e7583bff 100644 --- a/lib/model/filters/album.dart +++ b/lib/model/filters/album.dart @@ -18,6 +18,18 @@ class AlbumFilter extends CollectionFilter { const AlbumFilter(this.album, this.uniqueName); + AlbumFilter.fromJson(Map json) + : this( + json['album'], + json['uniqueName'], + ); + + Map toJson() => { + 'type': type, + 'album': album, + 'uniqueName': uniqueName, + }; + @override bool filter(ImageEntry entry) => entry.directory == album; diff --git a/lib/model/filters/favourite.dart b/lib/model/filters/favourite.dart index 9835fa68f..39d88b8ae 100644 --- a/lib/model/filters/favourite.dart +++ b/lib/model/filters/favourite.dart @@ -7,6 +7,10 @@ import 'package:flutter/widgets.dart'; class FavouriteFilter extends CollectionFilter { static const type = 'favourite'; + Map toJson() => { + 'type': type, + }; + @override bool filter(ImageEntry entry) => entry.isFavourite; diff --git a/lib/model/filters/filters.dart b/lib/model/filters/filters.dart index 2375c25e8..4b198f094 100644 --- a/lib/model/filters/filters.dart +++ b/lib/model/filters/filters.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/favourite.dart'; import 'package:aves/model/filters/location.dart'; @@ -20,6 +22,27 @@ abstract class CollectionFilter implements Comparable { TagFilter.type, ]; + static CollectionFilter fromJson(String jsonString) { + final jsonMap = jsonDecode(jsonString); + final type = jsonMap['type']; + switch (type) { + case AlbumFilter.type: + return AlbumFilter.fromJson(jsonMap); + case FavouriteFilter.type: + return FavouriteFilter(); + case LocationFilter.type: + return LocationFilter.fromJson(jsonMap); + case MimeFilter.type: + return MimeFilter.fromJson(jsonMap); + case QueryFilter.type: + return QueryFilter.fromJson(jsonMap); + case TagFilter.type: + return TagFilter.fromJson(jsonMap); + } + debugPrint('failed to parse filter from json=$jsonString'); + return null; + } + const CollectionFilter(); bool filter(ImageEntry entry); diff --git a/lib/model/filters/location.dart b/lib/model/filters/location.dart index 8e905a9f8..e348d6459 100644 --- a/lib/model/filters/location.dart +++ b/lib/model/filters/location.dart @@ -5,17 +5,30 @@ import 'package:flutter/widgets.dart'; class LocationFilter extends CollectionFilter { static const type = 'country'; + static const locationSeparator = ';'; final LocationLevel level; String _location; String _countryCode; LocationFilter(this.level, this._location) { - final split = _location.split(';'); + final split = _location.split(locationSeparator); if (split.isNotEmpty) _location = split[0]; if (split.length > 1) _countryCode = split[1]; } + LocationFilter.fromJson(Map json) + : this( + LocationLevel.values.firstWhere((v) => v.toString() == json['level'], orElse: () => null), + json['location'], + ); + + Map toJson() => { + 'type': type, + 'level': level.toString(), + 'location': _countryCode != null ? '$_location$locationSeparator$_countryCode' : _location, + }; + @override bool filter(ImageEntry entry) => entry.isLocated && ((level == LocationLevel.country && entry.addressDetails.countryName == _location) || (level == LocationLevel.place && entry.addressDetails.place == _location)); diff --git a/lib/model/filters/mime.dart b/lib/model/filters/mime.dart index 9e9664b8e..6ae0ff1f9 100644 --- a/lib/model/filters/mime.dart +++ b/lib/model/filters/mime.dart @@ -38,6 +38,16 @@ class MimeFilter extends CollectionFilter { _icon ??= AIcons.vector; } + MimeFilter.fromJson(Map json) + : this( + json['mime'], + ); + + Map toJson() => { + 'type': type, + 'mime': mime, + }; + static String displayType(String mime) { return mime.toUpperCase().replaceFirst(RegExp('.*/(X-)?'), '').replaceFirst('+XML', '').replaceFirst('VND.', ''); } diff --git a/lib/model/filters/query.dart b/lib/model/filters/query.dart index 1cd687e9d..b9a9f915d 100644 --- a/lib/model/filters/query.dart +++ b/lib/model/filters/query.dart @@ -32,6 +32,16 @@ class QueryFilter extends CollectionFilter { _filter = not ? (entry) => !entry.search(upQuery) : (entry) => entry.search(upQuery); } + QueryFilter.fromJson(Map json) + : this( + json['query'], + ); + + Map toJson() => { + 'type': type, + 'query': query, + }; + @override bool filter(ImageEntry entry) => _filter(entry); diff --git a/lib/model/filters/tag.dart b/lib/model/filters/tag.dart index 6d66c6a45..a25b5d630 100644 --- a/lib/model/filters/tag.dart +++ b/lib/model/filters/tag.dart @@ -10,6 +10,16 @@ class TagFilter extends CollectionFilter { const TagFilter(this.tag); + TagFilter.fromJson(Map json) + : this( + json['tag'], + ); + + Map toJson() => { + 'type': type, + 'tag': tag, + }; + @override bool filter(ImageEntry entry) => entry.xmpSubjects.contains(tag); diff --git a/lib/model/source/location.dart b/lib/model/source/location.dart index 0d60fa61d..74fe62f5d 100644 --- a/lib/model/source/location.dart +++ b/lib/model/source/location.dart @@ -1,3 +1,4 @@ +import 'package:aves/model/filters/location.dart'; import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_metadata.dart'; import 'package:aves/model/metadata_db.dart'; @@ -76,7 +77,7 @@ mixin LocationMixin on SourceBase { void updateLocations() { final locations = rawEntries.where((entry) => entry.isLocated).map((entry) => entry.addressDetails).toList(); List lister(String Function(AddressDetails a) f) => List.unmodifiable(locations.map(f).where((s) => s != null && s.isNotEmpty).toSet().toList()..sort(compareAsciiUpperCase)); - sortedCountries = lister((address) => '${address.countryName};${address.countryCode}'); + sortedCountries = lister((address) => '${address.countryName}${LocationFilter.locationSeparator}${address.countryCode}'); sortedPlaces = lister((address) => address.place); invalidateFilterEntryCounts(); @@ -86,7 +87,7 @@ mixin LocationMixin on SourceBase { Map getCountryEntries() { final locatedEntries = sortedEntriesForFilterList.where((entry) => entry.isLocated); return Map.fromEntries(sortedCountries.map((countryNameAndCode) { - final split = countryNameAndCode.split(';'); + final split = countryNameAndCode.split(LocationFilter.locationSeparator); ImageEntry entry; if (split.length > 1) { final countryCode = split[1]; diff --git a/lib/widgets/fullscreen/info/location_section.dart b/lib/widgets/fullscreen/info/location_section.dart index 70ce9abf6..665379805 100644 --- a/lib/widgets/fullscreen/info/location_section.dart +++ b/lib/widgets/fullscreen/info/location_section.dart @@ -80,7 +80,7 @@ class _LocationSectionState extends State { final address = entry.addressDetails; location = address.addressLine; final country = address.countryName; - if (country != null && country.isNotEmpty) filters.add(LocationFilter(LocationLevel.country, '$country;${address.countryCode}')); + if (country != null && country.isNotEmpty) filters.add(LocationFilter(LocationLevel.country, '$country${LocationFilter.locationSeparator}${address.countryCode}')); final place = address.place; if (place != null && place.isNotEmpty) filters.add(LocationFilter(LocationLevel.place, place)); } diff --git a/lib/widgets/home_page.dart b/lib/widgets/home_page.dart index b09537df5..753e3ab94 100644 --- a/lib/widgets/home_page.dart +++ b/lib/widgets/home_page.dart @@ -1,8 +1,6 @@ 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/mime_types.dart'; import 'package:aves/model/settings/home_page.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; @@ -37,6 +35,8 @@ class _HomePageState extends State { String _shortcutRouteName; List _shortcutFilters; + static const allowedShortcutRoutes = [CollectionPage.routeName, AlbumListPage.routeName, SearchPage.routeName]; + @override void initState() { super.initState(); @@ -87,11 +87,8 @@ class _HomePageState extends State { default: // do not use 'route' as extra key, as the Flutter framework acts on it final extraRoute = intentData['page']; - switch (extraRoute) { - case CollectionPage.routeName: - case AlbumListPage.routeName: - case SearchPage.routeName: - _shortcutRouteName = extraRoute; + if (allowedShortcutRoutes.contains(extraRoute)) { + _shortcutRouteName = extraRoute; } final extraFilters = intentData['filters']; _shortcutFilters = extraFilters != null ? (extraFilters as List).cast() : null; @@ -131,14 +128,7 @@ class _HomePageState extends State { routeName = CollectionPage.routeName; } else { routeName = _shortcutRouteName ?? settings.homePage.routeName; - filters = (_shortcutFilters ?? []).map((filterString) { - switch (filterString) { - case 'anyVideo': - return MimeFilter(MimeTypes.anyVideo); - } - debugPrint('failed to parse shortcut filter=$filterString'); - return null; - }); + filters = (_shortcutFilters ?? []).map(CollectionFilter.fromJson); } switch (routeName) { case AlbumListPage.routeName: diff --git a/lib/widgets/stats/stats.dart b/lib/widgets/stats/stats.dart index ecaa1415b..3ac7bcf85 100644 --- a/lib/widgets/stats/stats.dart +++ b/lib/widgets/stats/stats.dart @@ -36,7 +36,7 @@ class StatsPage extends StatelessWidget { final address = entry.addressDetails; var country = address.countryName; if (country != null && country.isNotEmpty) { - country += ';${address.countryCode}'; + country += '${LocationFilter.locationSeparator}${address.countryCode}'; entryCountPerCountry[country] = (entryCountPerCountry[country] ?? 0) + 1; } final place = address.place; diff --git a/test/model/filters_test.dart b/test/model/filters_test.dart new file mode 100644 index 000000000..7ebf5fe35 --- /dev/null +++ b/test/model/filters_test.dart @@ -0,0 +1,35 @@ +import 'dart:convert'; + +import 'package:aves/model/filters/album.dart'; +import 'package:aves/model/filters/favourite.dart'; +import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/filters/location.dart'; +import 'package:aves/model/filters/mime.dart'; +import 'package:aves/model/filters/query.dart'; +import 'package:aves/model/filters/tag.dart'; +import 'package:aves/model/mime_types.dart'; +import 'package:test/test.dart'; + +void main() { + test('Filter serialization', () { + CollectionFilter jsonRoundTrip(filter) => CollectionFilter.fromJson(jsonEncode(filter.toJson())); + + final album = AlbumFilter('path/to/album', 'album'); + expect(album, jsonRoundTrip(album)); + + final fav = FavouriteFilter(); + expect(fav, jsonRoundTrip(fav)); + + final location = LocationFilter(LocationLevel.country, 'France${LocationFilter.locationSeparator}FR'); + expect(location, jsonRoundTrip(location)); + + final mime = MimeFilter(MimeTypes.anyVideo); + expect(mime, jsonRoundTrip(mime)); + + final query = QueryFilter('some query'); + expect(query, jsonRoundTrip(query)); + + final tag = TagFilter('some tag'); + expect(tag, jsonRoundTrip(tag)); + }); +} From 96ee07225393e5ba44a1b3ac15674882e3fa204e Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Wed, 9 Sep 2020 17:16:29 +0900 Subject: [PATCH 09/13] shortcuts: improved icons --- android/app/build.gradle | 1 + .../deckers/thibault/aves/MainActivity.java | 19 +++++++++---------- .../main/res/drawable/ic_outline_movie.xml | 5 ----- .../main/res/drawable/ic_outline_search.xml | 5 ----- .../drawable/ic_shortcut_movie_foreground.xml | 15 +++++++++++++++ .../ic_shortcut_search_foreground.xml | 15 +++++++++++++++ .../mipmap-anydpi-v26/ic_shortcut_movie.xml | 5 +++++ .../mipmap-anydpi-v26/ic_shortcut_search.xml | 5 +++++ ...{ic_launcher_background.xml => colors.xml} | 1 + 9 files changed, 51 insertions(+), 20 deletions(-) delete mode 100644 android/app/src/main/res/drawable/ic_outline_movie.xml delete mode 100644 android/app/src/main/res/drawable/ic_outline_search.xml create mode 100644 android/app/src/main/res/drawable/ic_shortcut_movie_foreground.xml create mode 100644 android/app/src/main/res/drawable/ic_shortcut_search_foreground.xml create mode 100644 android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_movie.xml create mode 100644 android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_search.xml rename android/app/src/main/res/values/{ic_launcher_background.xml => colors.xml} (67%) diff --git a/android/app/build.gradle b/android/app/build.gradle index 4b05d4c59..4ddc01781 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -103,6 +103,7 @@ dependencies { // enable support for Java 8 language APIs (stream, optional, etc.) // coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.9' + implementation 'androidx.core:core:1.5.0-alpha02' // v1.5.0-alpha02 for ShortcutManagerCompat.setDynamicShortcuts implementation "androidx.exifinterface:exifinterface:1.2.0" implementation 'com.commonsware.cwac:document:0.4.1' implementation 'com.drewnoakes:metadata-extractor:2.14.0' diff --git a/android/app/src/main/java/deckers/thibault/aves/MainActivity.java b/android/app/src/main/java/deckers/thibault/aves/MainActivity.java index e4161da04..4b30fce3a 100644 --- a/android/app/src/main/java/deckers/thibault/aves/MainActivity.java +++ b/android/app/src/main/java/deckers/thibault/aves/MainActivity.java @@ -1,15 +1,15 @@ package deckers.thibault.aves; 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.os.Build; import android.os.Bundle; import android.util.Log; import androidx.annotation.RequiresApi; +import androidx.core.content.pm.ShortcutInfoCompat; +import androidx.core.content.pm.ShortcutManagerCompat; +import androidx.core.graphics.drawable.IconCompat; import java.util.ArrayList; import java.util.Arrays; @@ -85,25 +85,24 @@ public class MainActivity extends FlutterActivity { @RequiresApi(Build.VERSION_CODES.N_MR1) private void setupShortcuts() { - ShortcutManager shortcutManager = getSystemService(ShortcutManager.class); - // do not use 'route' as extra key, as the Flutter framework acts on it - ShortcutInfo search = new ShortcutInfo.Builder(this, "search") + ShortcutInfoCompat search = new ShortcutInfoCompat.Builder(this, "search") .setShortLabel(getString(R.string.search_shortcut_short_label)) - .setIcon(Icon.createWithResource(this, R.drawable.ic_outline_search)) + .setIcon(IconCompat.createWithResource(this, R.mipmap.ic_shortcut_search)) .setIntent(new Intent(Intent.ACTION_MAIN, null, this, MainActivity.class) .putExtra("page", "/search")) .build(); - ShortcutInfo videos = new ShortcutInfo.Builder(this, "videos") + ShortcutInfoCompat videos = new ShortcutInfoCompat.Builder(this, "videos") .setShortLabel(getString(R.string.videos_shortcut_short_label)) - .setIcon(Icon.createWithResource(this, R.drawable.ic_outline_movie)) + .setIcon(IconCompat.createWithResource(this, R.mipmap.ic_shortcut_movie)) .setIntent(new Intent(Intent.ACTION_MAIN, null, this, MainActivity.class) .putExtra("page", "/collection") .putExtra("filters", new String[]{"{\"type\":\"mime\",\"mime\":\"video/*\"}"})) .build(); - shortcutManager.setDynamicShortcuts(Arrays.asList(videos, search)); + + ShortcutManagerCompat.setDynamicShortcuts(this, Arrays.asList(videos, search)); } private void handleIntent(Intent intent) { diff --git a/android/app/src/main/res/drawable/ic_outline_movie.xml b/android/app/src/main/res/drawable/ic_outline_movie.xml deleted file mode 100644 index ca2e076e1..000000000 --- a/android/app/src/main/res/drawable/ic_outline_movie.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_outline_search.xml b/android/app/src/main/res/drawable/ic_outline_search.xml deleted file mode 100644 index 029cb754c..000000000 --- a/android/app/src/main/res/drawable/ic_outline_search.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_shortcut_movie_foreground.xml b/android/app/src/main/res/drawable/ic_shortcut_movie_foreground.xml new file mode 100644 index 000000000..0955cd1d5 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_shortcut_movie_foreground.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/android/app/src/main/res/drawable/ic_shortcut_search_foreground.xml b/android/app/src/main/res/drawable/ic_shortcut_search_foreground.xml new file mode 100644 index 000000000..fb2972b41 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_shortcut_search_foreground.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_movie.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_movie.xml new file mode 100644 index 000000000..a31978c2d --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_movie.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_search.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_search.xml new file mode 100644 index 000000000..1ab11ea64 --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_search.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/values/ic_launcher_background.xml b/android/app/src/main/res/values/colors.xml similarity index 67% rename from android/app/src/main/res/values/ic_launcher_background.xml rename to android/app/src/main/res/values/colors.xml index c5d5899fd..45ea13c03 100644 --- a/android/app/src/main/res/values/ic_launcher_background.xml +++ b/android/app/src/main/res/values/colors.xml @@ -1,4 +1,5 @@ #FFFFFF + #FFFFFF \ No newline at end of file From 89360ffa30e707f6bc5f454fb0867ca07902c3c9 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Wed, 9 Sep 2020 18:57:48 +0900 Subject: [PATCH 10/13] shortcuts: pin to filtered collection --- android/app/src/main/AndroidManifest.xml | 1 + .../deckers/thibault/aves/MainActivity.java | 2 + .../channel/calls/AppShortcutHandler.java | 64 ++++++++ .../ic_shortcut_collection_foreground.xml | 15 ++ .../ic_shortcut_collection.xml | 5 + lib/model/filters/album.dart | 1 + lib/model/filters/favourite.dart | 1 + lib/model/filters/filters.dart | 2 + lib/model/filters/location.dart | 1 + lib/model/filters/mime.dart | 1 + lib/model/filters/query.dart | 1 + lib/model/filters/tag.dart | 1 + lib/services/app_shortcut_service.dart | 37 +++++ lib/widgets/collection/app_bar.dart | 151 +++++++++--------- .../collection/collection_actions.dart | 13 ++ .../selection_action_delegate.dart | 2 +- lib/widgets/common/entry_actions.dart | 16 +- lib/widgets/common/icons.dart | 1 + 18 files changed, 239 insertions(+), 76 deletions(-) create mode 100644 android/app/src/main/java/deckers/thibault/aves/channel/calls/AppShortcutHandler.java create mode 100644 android/app/src/main/res/drawable/ic_shortcut_collection_foreground.xml create mode 100644 android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_collection.xml create mode 100644 lib/services/app_shortcut_service.dart create mode 100644 lib/widgets/collection/collection_actions.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index c1717c39a..7016ee892 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -46,6 +46,7 @@ filters = call.argument("filters"); + pin(label, filters); + result.success(null); + break; + } + default: + result.notImplemented(); + break; + } + } + + private void pin(String label, @Nullable List filters) { + if (!ShortcutManagerCompat.isRequestPinShortcutSupported(context) || filters == null) { + return; + } + + ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(context, "collection-" + TextUtils.join("-", filters)) + .setShortLabel(label) + .setIcon(IconCompat.createWithResource(context, R.mipmap.ic_shortcut_collection)) + .setIntent(new Intent(Intent.ACTION_MAIN, null, context, MainActivity.class) + .putExtra("page", "/collection") + .putExtra("filters", filters.toArray(new String[0]))) + .build(); + + ShortcutManagerCompat.requestPinShortcut(context, shortcut, null); + } +} diff --git a/android/app/src/main/res/drawable/ic_shortcut_collection_foreground.xml b/android/app/src/main/res/drawable/ic_shortcut_collection_foreground.xml new file mode 100644 index 000000000..45b1c8cb2 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_shortcut_collection_foreground.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_collection.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_collection.xml new file mode 100644 index 000000000..e069568b3 --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_collection.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/lib/model/filters/album.dart b/lib/model/filters/album.dart index 7e7583bff..f729f2329 100644 --- a/lib/model/filters/album.dart +++ b/lib/model/filters/album.dart @@ -24,6 +24,7 @@ class AlbumFilter extends CollectionFilter { json['uniqueName'], ); + @override Map toJson() => { 'type': type, 'album': album, diff --git a/lib/model/filters/favourite.dart b/lib/model/filters/favourite.dart index 39d88b8ae..fbf1194de 100644 --- a/lib/model/filters/favourite.dart +++ b/lib/model/filters/favourite.dart @@ -7,6 +7,7 @@ import 'package:flutter/widgets.dart'; class FavouriteFilter extends CollectionFilter { static const type = 'favourite'; + @override Map toJson() => { 'type': type, }; diff --git a/lib/model/filters/filters.dart b/lib/model/filters/filters.dart index 4b198f094..ab519743c 100644 --- a/lib/model/filters/filters.dart +++ b/lib/model/filters/filters.dart @@ -45,6 +45,8 @@ abstract class CollectionFilter implements Comparable { const CollectionFilter(); + Map toJson(); + bool filter(ImageEntry entry); bool get isUnique => true; diff --git a/lib/model/filters/location.dart b/lib/model/filters/location.dart index e348d6459..1042430d2 100644 --- a/lib/model/filters/location.dart +++ b/lib/model/filters/location.dart @@ -23,6 +23,7 @@ class LocationFilter extends CollectionFilter { json['location'], ); + @override Map toJson() => { 'type': type, 'level': level.toString(), diff --git a/lib/model/filters/mime.dart b/lib/model/filters/mime.dart index 6ae0ff1f9..54b6beb0a 100644 --- a/lib/model/filters/mime.dart +++ b/lib/model/filters/mime.dart @@ -43,6 +43,7 @@ class MimeFilter extends CollectionFilter { json['mime'], ); + @override Map toJson() => { 'type': type, 'mime': mime, diff --git a/lib/model/filters/query.dart b/lib/model/filters/query.dart index b9a9f915d..9714adc2b 100644 --- a/lib/model/filters/query.dart +++ b/lib/model/filters/query.dart @@ -37,6 +37,7 @@ class QueryFilter extends CollectionFilter { json['query'], ); + @override Map toJson() => { 'type': type, 'query': query, diff --git a/lib/model/filters/tag.dart b/lib/model/filters/tag.dart index a25b5d630..83c0d5ef1 100644 --- a/lib/model/filters/tag.dart +++ b/lib/model/filters/tag.dart @@ -15,6 +15,7 @@ class TagFilter extends CollectionFilter { json['tag'], ); + @override Map toJson() => { 'type': type, 'tag': tag, diff --git a/lib/services/app_shortcut_service.dart b/lib/services/app_shortcut_service.dart new file mode 100644 index 000000000..9008776f3 --- /dev/null +++ b/lib/services/app_shortcut_service.dart @@ -0,0 +1,37 @@ +import 'dart:convert'; + +import 'package:aves/model/filters/filters.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +class AppShortcutService { + static const platform = MethodChannel('deckers.thibault/aves/shortcut'); + + // this ability will not change over the lifetime of the app + static bool _canPin; + + static Future canPin() async { + if (_canPin != null) { + return SynchronousFuture(_canPin); + } + + try { + _canPin = await platform.invokeMethod('canPin'); + return _canPin; + } on PlatformException catch (e) { + debugPrint('canPin failed with code=${e.code}, exception=${e.message}, details=${e.details}}'); + } + return false; + } + + static Future pin(String label, Set filters) async { + try { + await platform.invokeMethod('pin', { + 'label': label, + 'filters': filters.map((filter) => jsonEncode(filter.toJson())).toList(), + }); + } on PlatformException catch (e) { + debugPrint('pin failed with code=${e.code}, exception=${e.message}, details=${e.details}'); + } + } +} diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart index a89e81e47..97cf2583f 100644 --- a/lib/widgets/collection/app_bar.dart +++ b/lib/widgets/collection/app_bar.dart @@ -4,7 +4,9 @@ import 'package:aves/main.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/enums.dart'; +import 'package:aves/services/app_shortcut_service.dart'; import 'package:aves/utils/durations.dart'; +import 'package:aves/widgets/collection/collection_actions.dart'; import 'package:aves/widgets/collection/filter_bar.dart'; import 'package:aves/widgets/collection/search/search_delegate.dart'; import 'package:aves/widgets/common/action_delegates/selection_action_delegate.dart'; @@ -39,6 +41,7 @@ class _CollectionAppBarState extends State with SingleTickerPr final TextEditingController _searchFieldController = TextEditingController(); SelectionActionDelegate _actionDelegate; AnimationController _browseToSelectAnimation; + Future _canAddShortcutsLoader; CollectionLens get collection => widget.collection; @@ -54,6 +57,7 @@ class _CollectionAppBarState extends State with SingleTickerPr duration: Durations.iconAnimation, vsync: this, ); + _canAddShortcutsLoader = AppShortcutService.canPin(); _registerWidget(widget); WidgetsBinding.instance.addPostFrameCallback((_) => _updateHeight()); } @@ -187,71 +191,80 @@ class _CollectionAppBarState extends State with SingleTickerPr ); }, )), - Builder( - builder: (context) => PopupMenuButton( - key: Key('appbar-menu-button'), - itemBuilder: (context) { - final hasSelection = collection.selection.isNotEmpty; - return [ - PopupMenuItem( - key: Key('menu-sort'), - value: CollectionAction.sort, - child: MenuRow(text: 'Sort...', icon: AIcons.sort), - ), - if (collection.sortFactor == EntrySortFactor.date) + FutureBuilder( + future: _canAddShortcutsLoader, + builder: (context, snapshot) { + final canAddShortcuts = snapshot.data ?? false; + return PopupMenuButton( + key: Key('appbar-menu-button'), + itemBuilder: (context) { + final hasSelection = collection.selection.isNotEmpty; + return [ PopupMenuItem( - key: Key('menu-group'), - value: CollectionAction.group, - child: MenuRow(text: 'Group...', icon: AIcons.group), + key: Key('menu-sort'), + value: CollectionAction.sort, + child: MenuRow(text: 'Sort...', icon: AIcons.sort), ), - if (collection.isBrowsing) ...[ - if (AvesApp.mode == AppMode.main) - if (kDebugMode) + if (collection.sortFactor == EntrySortFactor.date) + PopupMenuItem( + key: Key('menu-group'), + value: CollectionAction.group, + child: MenuRow(text: 'Group...', icon: AIcons.group), + ), + if (collection.isBrowsing) ...[ + if (AvesApp.mode == AppMode.main) + if (kDebugMode) + PopupMenuItem( + value: CollectionAction.refresh, + child: MenuRow(text: 'Refresh', icon: AIcons.refresh), + ), + PopupMenuItem( + value: CollectionAction.select, + child: MenuRow(text: 'Select', icon: AIcons.select), + ), + PopupMenuItem( + value: CollectionAction.stats, + child: MenuRow(text: 'Stats', icon: AIcons.stats), + ), + if (canAddShortcuts) PopupMenuItem( - value: CollectionAction.refresh, - child: MenuRow(text: 'Refresh', icon: AIcons.refresh), + value: CollectionAction.addShortcut, + child: MenuRow(text: 'Add shortcut', icon: AIcons.addShortcut), ), - PopupMenuItem( - value: CollectionAction.select, - child: MenuRow(text: 'Select', icon: AIcons.select), - ), - PopupMenuItem( - value: CollectionAction.stats, - child: MenuRow(text: 'Stats', icon: AIcons.stats), - ), - ], - if (collection.isSelecting) ...[ - PopupMenuDivider(), - PopupMenuItem( - value: CollectionAction.copy, - enabled: hasSelection, - child: MenuRow(text: 'Copy to album'), - ), - PopupMenuItem( - value: CollectionAction.move, - enabled: hasSelection, - child: MenuRow(text: 'Move to album'), - ), - PopupMenuItem( - value: CollectionAction.refreshMetadata, - enabled: hasSelection, - child: MenuRow(text: 'Refresh metadata'), - ), - PopupMenuDivider(), - PopupMenuItem( - value: CollectionAction.selectAll, - child: MenuRow(text: 'Select all'), - ), - PopupMenuItem( - value: CollectionAction.selectNone, - enabled: hasSelection, - child: MenuRow(text: 'Select none'), - ), - ] - ]; - }, - onSelected: _onCollectionActionSelected, - ), + ], + if (collection.isSelecting) ...[ + PopupMenuDivider(), + PopupMenuItem( + value: CollectionAction.copy, + enabled: hasSelection, + child: MenuRow(text: 'Copy to album'), + ), + PopupMenuItem( + value: CollectionAction.move, + enabled: hasSelection, + child: MenuRow(text: 'Move to album'), + ), + PopupMenuItem( + value: CollectionAction.refreshMetadata, + enabled: hasSelection, + child: MenuRow(text: 'Refresh metadata'), + ), + PopupMenuDivider(), + PopupMenuItem( + value: CollectionAction.selectAll, + child: MenuRow(text: 'Select all'), + ), + PopupMenuItem( + value: CollectionAction.selectNone, + enabled: hasSelection, + child: MenuRow(text: 'Select none'), + ), + ] + ]; + }, + onSelected: _onCollectionActionSelected, + ); + }, ), ]; } @@ -297,6 +310,9 @@ class _CollectionAppBarState extends State with SingleTickerPr case CollectionAction.stats: _goToStats(); break; + case CollectionAction.addShortcut: + unawaited(AppShortcutService.pin('Collection', collection.filters)); + break; case CollectionAction.group: final value = await showDialog( context: context, @@ -360,16 +376,3 @@ class _CollectionAppBarState extends State with SingleTickerPr ); } } - -enum CollectionAction { - copy, - group, - move, - refresh, - refreshMetadata, - select, - selectAll, - selectNone, - sort, - stats, -} diff --git a/lib/widgets/collection/collection_actions.dart b/lib/widgets/collection/collection_actions.dart new file mode 100644 index 000000000..c71ba093c --- /dev/null +++ b/lib/widgets/collection/collection_actions.dart @@ -0,0 +1,13 @@ +enum CollectionAction { + addShortcut, + copy, + group, + move, + refresh, + refreshMetadata, + select, + selectAll, + selectNone, + sort, + stats, +} diff --git a/lib/widgets/common/action_delegates/selection_action_delegate.dart b/lib/widgets/common/action_delegates/selection_action_delegate.dart index d7a4b286e..4f5fd8d9a 100644 --- a/lib/widgets/common/action_delegates/selection_action_delegate.dart +++ b/lib/widgets/common/action_delegates/selection_action_delegate.dart @@ -9,7 +9,7 @@ import 'package:aves/model/source/collection_source.dart'; import 'package:aves/services/android_app_service.dart'; import 'package:aves/services/image_file_service.dart'; import 'package:aves/utils/durations.dart'; -import 'package:aves/widgets/collection/app_bar.dart'; +import 'package:aves/widgets/collection/collection_actions.dart'; import 'package:aves/widgets/collection/empty.dart'; import 'package:aves/widgets/common/action_delegates/create_album_dialog.dart'; import 'package:aves/widgets/common/action_delegates/feedback.dart'; diff --git a/lib/widgets/common/entry_actions.dart b/lib/widgets/common/entry_actions.dart index 2e9e93a67..47500e61b 100644 --- a/lib/widgets/common/entry_actions.dart +++ b/lib/widgets/common/entry_actions.dart @@ -1,7 +1,21 @@ import 'package:aves/widgets/common/icons.dart'; import 'package:flutter/material.dart'; -enum EntryAction { delete, edit, info, open, openMap, print, rename, rotateCCW, rotateCW, setAs, share, toggleFavourite, debug } +enum EntryAction { + delete, + edit, + info, + open, + openMap, + print, + rename, + rotateCCW, + rotateCW, + setAs, + share, + toggleFavourite, + debug, +} class EntryActions { static const selection = [ diff --git a/lib/widgets/common/icons.dart b/lib/widgets/common/icons.dart index 42d2f9f2c..6f7d2b47c 100644 --- a/lib/widgets/common/icons.dart +++ b/lib/widgets/common/icons.dart @@ -24,6 +24,7 @@ class AIcons { static const IconData tag = OMIcons.localOffer; // actions + static const IconData addShortcut = OMIcons.bookmarkBorder; static const IconData clear = OMIcons.clear; static const IconData collapse = OMIcons.expandLess; static const IconData createAlbum = OMIcons.addCircleOutline; From 3d63453f9d02202aabcdc52655f819a0182df26e Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Wed, 9 Sep 2020 21:23:53 +0900 Subject: [PATCH 11/13] search from albums/countries/tags pages --- lib/widgets/collection/app_bar.dart | 31 ++++++++----------- lib/widgets/common/app_bar_title.dart | 27 ++++++++++++++++ lib/widgets/filter_grids/albums_page.dart | 2 -- .../filter_grids/filter_grid_page.dart | 24 ++++++++++++-- lib/widgets/filter_grids/search_button.dart | 5 ++- 5 files changed, 66 insertions(+), 23 deletions(-) create mode 100644 lib/widgets/common/app_bar_title.dart diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart index 97cf2583f..c8b08ebbe 100644 --- a/lib/widgets/collection/app_bar.dart +++ b/lib/widgets/collection/app_bar.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:aves/main.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; +import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/enums.dart'; import 'package:aves/services/app_shortcut_service.dart'; import 'package:aves/utils/durations.dart'; @@ -11,11 +12,13 @@ import 'package:aves/widgets/collection/filter_bar.dart'; import 'package:aves/widgets/collection/search/search_delegate.dart'; import 'package:aves/widgets/common/action_delegates/selection_action_delegate.dart'; import 'package:aves/widgets/common/app_bar_subtitle.dart'; +import 'package:aves/widgets/common/app_bar_title.dart'; import 'package:aves/widgets/common/aves_selection_dialog.dart'; import 'package:aves/widgets/common/data_providers/media_store_collection_provider.dart'; import 'package:aves/widgets/common/entry_actions.dart'; import 'package:aves/widgets/common/icons.dart'; import 'package:aves/widgets/common/menu_row.dart'; +import 'package:aves/widgets/filter_grids/search_button.dart'; import 'package:aves/widgets/stats/stats.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -45,6 +48,8 @@ class _CollectionAppBarState extends State with SingleTickerPr CollectionLens get collection => widget.collection; + CollectionSource get source => collection.source; + bool get hasFilters => collection.filters.isNotEmpty; @override @@ -95,7 +100,6 @@ class _CollectionAppBarState extends State with SingleTickerPr return AnimatedBuilder( animation: collection.filterChangeNotifier, builder: (context, child) => SliverAppBar( - titleSpacing: 0, leading: _buildAppBarLeading(), title: _buildAppBarTitle(), actions: _buildActions(), @@ -105,6 +109,7 @@ class _CollectionAppBarState extends State with SingleTickerPr onPressed: collection.removeFilter, ) : null, + titleSpacing: 0, floating: true, ), ); @@ -145,20 +150,12 @@ class _CollectionAppBarState extends State with SingleTickerPr if (AvesApp.mode == AppMode.main) { title = SourceStateAwareAppBarTitle( title: title, - source: collection.source, + source: source, ); } - return GestureDetector( + return TappableAppBarTitle( onTap: _goToSearch, - // use a `Container` with a dummy color to make it expand - // so that we can also detect taps around the title `Text` - child: Container( - alignment: AlignmentDirectional.centerStart, - padding: EdgeInsets.symmetric(horizontal: NavigationToolbar.kMiddleSpacing), - color: Colors.transparent, - height: kToolbarHeight, - child: title, - ), + title: title, ); } else if (collection.isSelecting) { return AnimatedBuilder( @@ -175,10 +172,9 @@ class _CollectionAppBarState extends State with SingleTickerPr List _buildActions() { return [ if (collection.isBrowsing) - IconButton( - key: Key('search-button'), - icon: Icon(AIcons.search), - onPressed: _goToSearch, + SearchButton( + source, + parentCollection: collection, ), if (collection.isSelecting) ...EntryActions.selection.map((action) => AnimatedBuilder( @@ -292,10 +288,9 @@ class _CollectionAppBarState extends State with SingleTickerPr _actionDelegate.onCollectionActionSelected(context, action); break; case CollectionAction.refresh: - final source = collection.source; if (source is MediaStoreSource) { source.clearEntries(); - unawaited(source.refresh()); + unawaited((source as MediaStoreSource).refresh()); } break; case CollectionAction.select: diff --git a/lib/widgets/common/app_bar_title.dart b/lib/widgets/common/app_bar_title.dart new file mode 100644 index 000000000..f23b1e844 --- /dev/null +++ b/lib/widgets/common/app_bar_title.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +class TappableAppBarTitle extends StatelessWidget { + final GestureTapCallback onTap; + final Widget title; + + const TappableAppBarTitle({ + this.onTap, + @required this.title, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + // use a `Container` with a dummy color to make it expand + // so that we can also detect taps around the title `Text` + child: Container( + alignment: AlignmentDirectional.centerStart, + padding: EdgeInsets.symmetric(horizontal: NavigationToolbar.kMiddleSpacing), + color: Colors.transparent, + height: kToolbarHeight, + child: title, + ), + ); + } +} diff --git a/lib/widgets/filter_grids/albums_page.dart b/lib/widgets/filter_grids/albums_page.dart index dc6539b94..495b3cdf9 100644 --- a/lib/widgets/filter_grids/albums_page.dart +++ b/lib/widgets/filter_grids/albums_page.dart @@ -11,7 +11,6 @@ import 'package:aves/widgets/common/aves_selection_dialog.dart'; import 'package:aves/widgets/common/icons.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/search_button.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; @@ -54,7 +53,6 @@ class AlbumListPage extends StatelessWidget { List _buildActions(BuildContext context) { return [ - SearchButton(source), PopupMenuButton( key: Key('appbar-menu-button'), itemBuilder: (context) { diff --git a/lib/widgets/filter_grids/filter_grid_page.dart b/lib/widgets/filter_grids/filter_grid_page.dart index 5daaa0473..84414dc32 100644 --- a/lib/widgets/filter_grids/filter_grid_page.dart +++ b/lib/widgets/filter_grids/filter_grid_page.dart @@ -7,12 +7,15 @@ import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/utils/durations.dart'; import 'package:aves/widgets/collection/collection_page.dart'; +import 'package:aves/widgets/collection/search/search_delegate.dart'; import 'package:aves/widgets/common/app_bar_subtitle.dart'; +import 'package:aves/widgets/common/app_bar_title.dart'; import 'package:aves/widgets/common/aves_filter_chip.dart'; import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart'; import 'package:aves/widgets/common/double_back_pop.dart'; import 'package:aves/widgets/drawer/app_drawer.dart'; import 'package:aves/widgets/filter_grids/decorated_filter_chip.dart'; +import 'package:aves/widgets/filter_grids/search_button.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:provider/provider.dart'; @@ -40,10 +43,17 @@ class FilterNavigationPage extends StatelessWidget { source: source, appBar: SliverAppBar( title: SourceStateAwareAppBarTitle( - title: Text(title), + title: TappableAppBarTitle( + onTap: () => _goToSearch(context), + title: Text(title), + ), source: source, ), - actions: actions, + actions: [ + SearchButton(source), + ...(actions ?? []), + ], + titleSpacing: 0, floating: true, ), filterEntries: filterEntries, @@ -69,6 +79,16 @@ class FilterNavigationPage extends StatelessWidget { ), ); } + + void _goToSearch(BuildContext context) { + Navigator.push( + context, + SearchPageRoute( + delegate: ImageSearchDelegate( + source: source, + ), + )); + } } class FilterGridPage extends StatelessWidget { diff --git a/lib/widgets/filter_grids/search_button.dart b/lib/widgets/filter_grids/search_button.dart index 6a8577700..82628913f 100644 --- a/lib/widgets/filter_grids/search_button.dart +++ b/lib/widgets/filter_grids/search_button.dart @@ -1,3 +1,4 @@ +import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/widgets/collection/search/search_delegate.dart'; import 'package:aves/widgets/common/icons.dart'; @@ -5,8 +6,9 @@ import 'package:flutter/material.dart'; class SearchButton extends StatelessWidget { final CollectionSource source; + final CollectionLens parentCollection; - const SearchButton(this.source); + const SearchButton(this.source, {this.parentCollection}); @override Widget build(BuildContext context) { @@ -23,6 +25,7 @@ class SearchButton extends StatelessWidget { SearchPageRoute( delegate: ImageSearchDelegate( source: source, + parentCollection: parentCollection, ), )); } From 23e0d634e480148d6b770f587ca84bb65bc506b2 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Wed, 9 Sep 2020 21:33:36 +0900 Subject: [PATCH 12/13] fixed app bar layout --- lib/widgets/collection/app_bar.dart | 2 +- lib/widgets/common/app_bar_title.dart | 6 +++--- lib/widgets/filter_grids/filter_grid_page.dart | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart index c8b08ebbe..f83b8dc36 100644 --- a/lib/widgets/collection/app_bar.dart +++ b/lib/widgets/collection/app_bar.dart @@ -155,7 +155,7 @@ class _CollectionAppBarState extends State with SingleTickerPr } return TappableAppBarTitle( onTap: _goToSearch, - title: title, + child: title, ); } else if (collection.isSelecting) { return AnimatedBuilder( diff --git a/lib/widgets/common/app_bar_title.dart b/lib/widgets/common/app_bar_title.dart index f23b1e844..d00745b8c 100644 --- a/lib/widgets/common/app_bar_title.dart +++ b/lib/widgets/common/app_bar_title.dart @@ -2,11 +2,11 @@ import 'package:flutter/material.dart'; class TappableAppBarTitle extends StatelessWidget { final GestureTapCallback onTap; - final Widget title; + final Widget child; const TappableAppBarTitle({ this.onTap, - @required this.title, + @required this.child, }); @override @@ -20,7 +20,7 @@ class TappableAppBarTitle extends StatelessWidget { padding: EdgeInsets.symmetric(horizontal: NavigationToolbar.kMiddleSpacing), color: Colors.transparent, height: kToolbarHeight, - child: title, + child: child, ), ); } diff --git a/lib/widgets/filter_grids/filter_grid_page.dart b/lib/widgets/filter_grids/filter_grid_page.dart index 84414dc32..ced774c4b 100644 --- a/lib/widgets/filter_grids/filter_grid_page.dart +++ b/lib/widgets/filter_grids/filter_grid_page.dart @@ -42,12 +42,12 @@ class FilterNavigationPage extends StatelessWidget { return FilterGridPage( source: source, appBar: SliverAppBar( - title: SourceStateAwareAppBarTitle( - title: TappableAppBarTitle( - onTap: () => _goToSearch(context), + title: TappableAppBarTitle( + onTap: () => _goToSearch(context), + child: SourceStateAwareAppBarTitle( title: Text(title), + source: source, ), - source: source, ), actions: [ SearchButton(source), From 7fcd0da51b8a778cd056819324e99c9ae1b3996b Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Wed, 9 Sep 2020 21:56:48 +0900 Subject: [PATCH 13/13] version bump --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index f5c521fa7..9fbb97e27 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.1.9+21 +version: 1.1.10+22 # video_player (as of v0.10.8+2, backed by ExoPlayer): # - does not support content URIs (by default, but trivial by fork)