From 0120efc80056f59f72c931909fc0cf8e972bd9df Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Tue, 14 Mar 2023 17:57:31 +0100 Subject: [PATCH 01/11] pub upgrade --- plugins/aves_magnifier/pubspec.lock | 2 +- plugins/aves_magnifier/pubspec.yaml | 2 +- plugins/aves_map/pubspec.lock | 6 +++--- plugins/aves_map/pubspec.yaml | 2 +- plugins/aves_platform_meta/pubspec.lock | 2 +- plugins/aves_platform_meta/pubspec.yaml | 2 +- plugins/aves_report/pubspec.lock | 2 +- plugins/aves_report/pubspec.yaml | 2 +- plugins/aves_report_console/pubspec.lock | 2 +- plugins/aves_report_console/pubspec.yaml | 2 +- plugins/aves_report_crashlytics/pubspec.lock | 2 +- plugins/aves_report_crashlytics/pubspec.yaml | 2 +- plugins/aves_services/pubspec.lock | 6 +++--- plugins/aves_services/pubspec.yaml | 2 +- plugins/aves_services_google/pubspec.lock | 6 +++--- plugins/aves_services_google/pubspec.yaml | 2 +- plugins/aves_services_huawei/pubspec.lock | 6 +++--- plugins/aves_services_huawei/pubspec.yaml | 2 +- plugins/aves_services_none/pubspec.lock | 6 +++--- plugins/aves_services_none/pubspec.yaml | 2 +- plugins/aves_ui/pubspec.lock | 2 +- plugins/aves_ui/pubspec.yaml | 2 +- pubspec.lock | 10 +++++----- pubspec.yaml | 2 +- 24 files changed, 38 insertions(+), 38 deletions(-) diff --git a/plugins/aves_magnifier/pubspec.lock b/plugins/aves_magnifier/pubspec.lock index bb225830d..26dfee840 100644 --- a/plugins/aves_magnifier/pubspec.lock +++ b/plugins/aves_magnifier/pubspec.lock @@ -108,5 +108,5 @@ packages: source: hosted version: "2.1.4" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.4 <3.0.0" flutter: ">=1.16.0" diff --git a/plugins/aves_magnifier/pubspec.yaml b/plugins/aves_magnifier/pubspec.yaml index 3ef32e0bd..083facfdd 100644 --- a/plugins/aves_magnifier/pubspec.yaml +++ b/plugins/aves_magnifier/pubspec.yaml @@ -3,7 +3,7 @@ version: 0.0.1 publish_to: none environment: - sdk: ">=2.19.0 <3.0.0" + sdk: ">=2.19.4 <3.0.0" dependencies: flutter: diff --git a/plugins/aves_map/pubspec.lock b/plugins/aves_map/pubspec.lock index bcfc9e221..f0229daf1 100644 --- a/plugins/aves_map/pubspec.lock +++ b/plugins/aves_map/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" aves_ui: dependency: "direct main" description: @@ -283,5 +283,5 @@ packages: source: hosted version: "2.0.0" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.4 <3.0.0" flutter: ">=3.3.0" diff --git a/plugins/aves_map/pubspec.yaml b/plugins/aves_map/pubspec.yaml index 267d70a7f..15b1316cd 100644 --- a/plugins/aves_map/pubspec.yaml +++ b/plugins/aves_map/pubspec.yaml @@ -3,7 +3,7 @@ version: 0.0.1 publish_to: none environment: - sdk: ">=2.19.0 <3.0.0" + sdk: ">=2.19.4 <3.0.0" dependencies: flutter: diff --git a/plugins/aves_platform_meta/pubspec.lock b/plugins/aves_platform_meta/pubspec.lock index b0106103f..d4fa7deb8 100644 --- a/plugins/aves_platform_meta/pubspec.lock +++ b/plugins/aves_platform_meta/pubspec.lock @@ -84,4 +84,4 @@ packages: source: hosted version: "2.1.4" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.4 <3.0.0" diff --git a/plugins/aves_platform_meta/pubspec.yaml b/plugins/aves_platform_meta/pubspec.yaml index 0cc9a2174..a33a46065 100644 --- a/plugins/aves_platform_meta/pubspec.yaml +++ b/plugins/aves_platform_meta/pubspec.yaml @@ -3,7 +3,7 @@ version: 0.0.1 publish_to: none environment: - sdk: ">=2.19.0 <3.0.0" + sdk: ">=2.19.4 <3.0.0" dependencies: flutter: diff --git a/plugins/aves_report/pubspec.lock b/plugins/aves_report/pubspec.lock index 8ac10f808..6e38c33fc 100644 --- a/plugins/aves_report/pubspec.lock +++ b/plugins/aves_report/pubspec.lock @@ -76,4 +76,4 @@ packages: source: hosted version: "2.1.4" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.4 <3.0.0" diff --git a/plugins/aves_report/pubspec.yaml b/plugins/aves_report/pubspec.yaml index ce2029d14..25b1d5315 100644 --- a/plugins/aves_report/pubspec.yaml +++ b/plugins/aves_report/pubspec.yaml @@ -3,7 +3,7 @@ version: 0.0.1 publish_to: none environment: - sdk: ">=2.19.0 <3.0.0" + sdk: ">=2.19.4 <3.0.0" dependencies: flutter: diff --git a/plugins/aves_report_console/pubspec.lock b/plugins/aves_report_console/pubspec.lock index 9e4ee23d3..e92d2d71c 100644 --- a/plugins/aves_report_console/pubspec.lock +++ b/plugins/aves_report_console/pubspec.lock @@ -83,4 +83,4 @@ packages: source: hosted version: "2.1.4" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.4 <3.0.0" diff --git a/plugins/aves_report_console/pubspec.yaml b/plugins/aves_report_console/pubspec.yaml index 2face2183..ee9350738 100644 --- a/plugins/aves_report_console/pubspec.yaml +++ b/plugins/aves_report_console/pubspec.yaml @@ -3,7 +3,7 @@ version: 0.0.1 publish_to: none environment: - sdk: ">=2.19.0 <3.0.0" + sdk: ">=2.19.4 <3.0.0" dependencies: flutter: diff --git a/plugins/aves_report_crashlytics/pubspec.lock b/plugins/aves_report_crashlytics/pubspec.lock index ec6cd74a7..04cf57c9f 100644 --- a/plugins/aves_report_crashlytics/pubspec.lock +++ b/plugins/aves_report_crashlytics/pubspec.lock @@ -245,5 +245,5 @@ packages: source: hosted version: "2.1.4" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.4 <3.0.0" flutter: ">=1.20.0" diff --git a/plugins/aves_report_crashlytics/pubspec.yaml b/plugins/aves_report_crashlytics/pubspec.yaml index aea210f04..75a38426c 100644 --- a/plugins/aves_report_crashlytics/pubspec.yaml +++ b/plugins/aves_report_crashlytics/pubspec.yaml @@ -3,7 +3,7 @@ version: 0.0.1 publish_to: none environment: - sdk: ">=2.19.0 <3.0.0" + sdk: ">=2.19.4 <3.0.0" dependencies: flutter: diff --git a/plugins/aves_services/pubspec.lock b/plugins/aves_services/pubspec.lock index 184bee844..acb670bd7 100644 --- a/plugins/aves_services/pubspec.lock +++ b/plugins/aves_services/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" aves_map: dependency: "direct main" description: @@ -290,5 +290,5 @@ packages: source: hosted version: "2.0.0" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.4 <3.0.0" flutter: ">=3.3.0" diff --git a/plugins/aves_services/pubspec.yaml b/plugins/aves_services/pubspec.yaml index cc949a8db..d2e60c871 100644 --- a/plugins/aves_services/pubspec.yaml +++ b/plugins/aves_services/pubspec.yaml @@ -3,7 +3,7 @@ version: 0.0.1 publish_to: none environment: - sdk: ">=2.19.0 <3.0.0" + sdk: ">=2.19.4 <3.0.0" dependencies: flutter: diff --git a/plugins/aves_services_google/pubspec.lock b/plugins/aves_services_google/pubspec.lock index 4b7953c5b..7584f000a 100644 --- a/plugins/aves_services_google/pubspec.lock +++ b/plugins/aves_services_google/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" aves_map: dependency: "direct main" description: @@ -406,5 +406,5 @@ packages: source: hosted version: "2.0.0" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.4 <3.0.0" flutter: ">=3.3.0" diff --git a/plugins/aves_services_google/pubspec.yaml b/plugins/aves_services_google/pubspec.yaml index e9f15853f..259ac7a04 100644 --- a/plugins/aves_services_google/pubspec.yaml +++ b/plugins/aves_services_google/pubspec.yaml @@ -3,7 +3,7 @@ version: 0.0.1 publish_to: none environment: - sdk: ">=2.19.0 <3.0.0" + sdk: ">=2.19.4 <3.0.0" dependencies: flutter: diff --git a/plugins/aves_services_huawei/pubspec.lock b/plugins/aves_services_huawei/pubspec.lock index 394c2aa13..91c859936 100644 --- a/plugins/aves_services_huawei/pubspec.lock +++ b/plugins/aves_services_huawei/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" aves_map: dependency: "direct main" description: @@ -336,5 +336,5 @@ packages: source: hosted version: "2.0.0" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.4 <3.0.0" flutter: ">=3.3.0" diff --git a/plugins/aves_services_huawei/pubspec.yaml b/plugins/aves_services_huawei/pubspec.yaml index c50e4c239..b462c1a74 100644 --- a/plugins/aves_services_huawei/pubspec.yaml +++ b/plugins/aves_services_huawei/pubspec.yaml @@ -3,7 +3,7 @@ version: 0.0.1 publish_to: none environment: - sdk: ">=2.19.0 <3.0.0" + sdk: ">=2.19.4 <3.0.0" dependencies: flutter: diff --git a/plugins/aves_services_none/pubspec.lock b/plugins/aves_services_none/pubspec.lock index c43870e1d..4a10db68e 100644 --- a/plugins/aves_services_none/pubspec.lock +++ b/plugins/aves_services_none/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" aves_map: dependency: "direct main" description: @@ -297,5 +297,5 @@ packages: source: hosted version: "2.0.0" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.4 <3.0.0" flutter: ">=3.3.0" diff --git a/plugins/aves_services_none/pubspec.yaml b/plugins/aves_services_none/pubspec.yaml index a2b172f39..691b2f7b8 100644 --- a/plugins/aves_services_none/pubspec.yaml +++ b/plugins/aves_services_none/pubspec.yaml @@ -3,7 +3,7 @@ version: 0.0.1 publish_to: none environment: - sdk: ">=2.19.0 <3.0.0" + sdk: ">=2.19.4 <3.0.0" dependencies: flutter: diff --git a/plugins/aves_ui/pubspec.lock b/plugins/aves_ui/pubspec.lock index 8ac10f808..6e38c33fc 100644 --- a/plugins/aves_ui/pubspec.lock +++ b/plugins/aves_ui/pubspec.lock @@ -76,4 +76,4 @@ packages: source: hosted version: "2.1.4" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.4 <3.0.0" diff --git a/plugins/aves_ui/pubspec.yaml b/plugins/aves_ui/pubspec.yaml index c39d0df10..3b715f44f 100644 --- a/plugins/aves_ui/pubspec.yaml +++ b/plugins/aves_ui/pubspec.yaml @@ -3,7 +3,7 @@ version: 0.0.1 publish_to: none environment: - sdk: ">=2.19.0 <3.0.0" + sdk: ">=2.19.4 <3.0.0" dependencies: flutter: diff --git a/pubspec.lock b/pubspec.lock index 33e622d23..614344212 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1214,18 +1214,18 @@ packages: dependency: "direct main" description: name: sqflite - sha256: "851d5040552cf911f4cabda08d003eca76b27da3ed0002978272e27c8fbf8ecc" + sha256: "500d6fec583d2c021f2d25a056d96654f910662c64f836cd2063167b8f1fa758" url: "https://pub.dev" source: hosted - version: "2.2.5" + version: "2.2.6" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: bfd6973aaeeb93475bc0d875ac9aefddf7965ef22ce09790eb963992ffc5183f + sha256: "963dad8c4aa2f814ce7d2d5b1da2f36f31bd1a439d8f27e3dc189bb9d26bc684" url: "https://pub.dev" source: hosted - version: "2.4.2+2" + version: "2.4.3" stack_trace: dependency: transitive description: @@ -1508,5 +1508,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.4 <3.0.0" flutter: ">=3.7.7" diff --git a/pubspec.yaml b/pubspec.yaml index 9d33a92ca..c091321ea 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,7 +14,7 @@ environment: # this project bundles Flutter SDK via `flutter_wrapper` # cf https://github.com/passsy/flutter_wrapper flutter: 3.7.7 - sdk: ">=2.19.0 <3.0.0" + sdk: ">=2.19.4 <3.0.0" # following https://github.blog/2021-09-01-improving-git-protocol-security-github/ # dependency GitHub repos should be referenced via `https://`, not `git://` From 5784607130f2b5448cb63dcffd8440ea6ee78060 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Tue, 14 Mar 2023 20:31:18 +0100 Subject: [PATCH 02/11] refactor --- lib/model/covers.dart | 2 +- lib/model/db/db_metadata.dart | 2 +- lib/model/db/db_metadata_sqflite.dart | 2 +- .../{entry_cache.dart => entry/cache.dart} | 0 .../{entry_dirs.dart => entry/dirs.dart} | 0 lib/model/{ => entry}/entry.dart | 427 ++----------- lib/model/entry/extensions/catalog.dart | 57 ++ lib/model/entry/extensions/favourites.dart | 26 + lib/model/entry/extensions/images.dart | 79 +++ lib/model/entry/extensions/info.dart | 161 +++++ lib/model/entry/extensions/location.dart | 89 +++ .../entry/extensions/metadata_edition.dart | 590 ++++++++++++++++++ lib/model/entry/extensions/multipage.dart | 53 ++ lib/model/entry/extensions/props.dart | 119 ++++ lib/model/entry/origins.dart | 6 + lib/model/entry/sort.dart | 38 ++ lib/model/entry_extensions/geo.dart | 0 .../images.dart} | 0 .../info.dart} | 0 .../metadata_edition.dart} | 0 lib/model/entry_extensions/multipage.dart | 0 lib/model/favourites.dart | 2 +- lib/model/filters/aspect_ratio.dart | 1 + lib/model/filters/coordinate.dart | 1 + lib/model/filters/favourite.dart | 3 +- lib/model/filters/filters.dart | 2 +- lib/model/filters/placeholder.dart | 4 +- lib/model/filters/query.dart | 3 +- lib/model/filters/trash.dart | 2 +- lib/model/filters/type.dart | 2 + lib/model/geotiff.dart | 4 +- lib/model/multipage.dart | 3 +- lib/model/naming_pattern.dart | 2 +- lib/model/settings/enums/video_loop_mode.dart | 4 +- lib/model/settings/enums/widget_shape.dart | 3 +- lib/model/source/album.dart | 2 +- lib/model/source/collection_lens.dart | 14 +- lib/model/source/collection_source.dart | 7 +- lib/model/source/events.dart | 2 +- lib/model/source/location/country.dart | 2 +- lib/model/source/location/location.dart | 3 +- lib/model/source/location/place.dart | 2 +- lib/model/source/media_store_source.dart | 3 +- lib/model/source/tag.dart | 3 +- lib/model/video/metadata.dart | 2 +- lib/services/android_app_service.dart | 3 +- lib/services/android_debug_service.dart | 2 +- lib/services/media/embedded_data_service.dart | 2 +- lib/services/media/media_edit_service.dart | 2 +- lib/services/media/media_fetch_service.dart | 2 +- lib/services/media/media_session_service.dart | 6 +- lib/services/media/media_store_service.dart | 2 +- .../metadata/metadata_edit_service.dart | 2 +- .../metadata/metadata_fetch_service.dart | 4 +- .../metadata/svg_metadata_service.dart | 2 +- lib/widget_common.dart | 5 +- lib/widgets/collection/app_bar.dart | 2 +- lib/widgets/collection/collection_grid.dart | 2 +- lib/widgets/collection/collection_page.dart | 2 +- .../collection/draggable_thumb_label.dart | 2 +- .../collection/entry_set_action_delegate.dart | 6 +- .../collection/grid/headers/album.dart | 2 +- lib/widgets/collection/grid/headers/any.dart | 2 +- lib/widgets/collection/grid/list_details.dart | 3 +- .../collection/grid/section_layout.dart | 3 +- lib/widgets/collection/grid/tile.dart | 2 +- lib/widgets/collection/query_bar.dart | 2 +- .../quick_choosers/share_button.dart | 3 +- .../action_controls/togglers/favourite.dart | 3 +- .../common/action_controls/togglers/mute.dart | 2 +- .../common/action_controls/togglers/play.dart | 2 +- .../common/action_mixins/entry_editor.dart | 5 +- .../common/action_mixins/entry_storage.dart | 4 +- .../action_mixins/permission_aware.dart | 2 +- .../common/action_mixins/size_aware.dart | 2 +- lib/widgets/common/grid/theme.dart | 5 +- lib/widgets/common/identity/aves_icons.dart | 4 +- lib/widgets/common/map/geo_map.dart | 8 +- lib/widgets/common/thumbnail/decorated.dart | 3 +- lib/widgets/common/thumbnail/error.dart | 2 +- lib/widgets/common/thumbnail/image.dart | 5 +- .../common/thumbnail/notifications.dart | 2 +- lib/widgets/common/thumbnail/overlay.dart | 2 +- lib/widgets/common/thumbnail/scroller.dart | 2 +- lib/widgets/debug/database.dart | 2 +- lib/widgets/dialogs/add_shortcut_dialog.dart | 2 +- lib/widgets/dialogs/convert_entry_dialog.dart | 5 +- .../entry_editors/edit_date_dialog.dart | 2 +- .../edit_description_dialog.dart | 2 +- .../entry_editors/edit_location_dialog.dart | 5 +- .../entry_editors/edit_rating_dialog.dart | 2 +- .../entry_editors/rename_entry_dialog.dart | 2 +- .../entry_editors/rename_entry_set_page.dart | 2 +- .../entry_editors/tag_editor_page.dart | 2 +- .../cover_selection_dialog.dart | 2 +- lib/widgets/dialogs/item_picker.dart | 2 +- .../dialogs/pick_dialogs/item_pick_page.dart | 2 +- .../video_stream_selection_dialog.dart | 7 +- lib/widgets/filter_grids/albums_page.dart | 1 + .../common/action_delegates/album_set.dart | 2 +- .../common/action_delegates/chip_set.dart | 2 +- .../filter_grids/common/filter_grid_page.dart | 3 +- .../filter_grids/common/list_details.dart | 2 +- lib/widgets/home_page.dart | 3 +- lib/widgets/home_widget.dart | 4 +- lib/widgets/map/map_info_row.dart | 3 +- lib/widgets/map/map_page.dart | 5 +- lib/widgets/stats/date/histogram.dart | 5 +- lib/widgets/stats/stats_page.dart | 2 +- .../viewer/action/entry_action_delegate.dart | 14 +- .../action/entry_info_action_delegate.dart | 8 +- lib/widgets/viewer/action/printer.dart | 6 +- .../viewer/action/single_entry_editor.dart | 4 +- .../viewer/action/video_action_delegate.dart | 13 +- lib/widgets/viewer/controls/controller.dart | 2 +- .../viewer/controls/notifications.dart | 6 +- lib/widgets/viewer/debug/db.dart | 2 +- lib/widgets/viewer/debug/debug_page.dart | 8 +- lib/widgets/viewer/debug/metadata.dart | 2 +- .../viewer/entry_horizontal_pager.dart | 4 +- lib/widgets/viewer/entry_vertical_pager.dart | 21 +- lib/widgets/viewer/entry_viewer_page.dart | 2 +- lib/widgets/viewer/entry_viewer_stack.dart | 34 +- lib/widgets/viewer/hero.dart | 2 +- lib/widgets/viewer/info/basic_section.dart | 5 +- .../info/embedded/embedded_data_opener.dart | 2 +- lib/widgets/viewer/info/info_app_bar.dart | 3 +- lib/widgets/viewer/info/info_page.dart | 3 +- lib/widgets/viewer/info/info_search.dart | 2 +- lib/widgets/viewer/info/location_section.dart | 3 +- .../info/metadata/metadata_dir_tile.dart | 2 +- .../info/metadata/metadata_section.dart | 4 +- .../info/metadata/metadata_thumbnail.dart | 2 +- lib/widgets/viewer/info/metadata/tv_page.dart | 2 +- lib/widgets/viewer/multipage/conductor.dart | 2 +- lib/widgets/viewer/multipage/controller.dart | 3 +- lib/widgets/viewer/overlay/bottom.dart | 3 +- lib/widgets/viewer/overlay/details/date.dart | 3 +- .../viewer/overlay/details/details.dart | 2 +- .../viewer/overlay/details/location.dart | 3 +- .../overlay/details/position_title.dart | 2 +- .../viewer/overlay/details/rating_tags.dart | 2 +- lib/widgets/viewer/overlay/panorama.dart | 2 +- .../viewer/overlay/selection_button.dart | 2 +- .../viewer/overlay/thumbnail_preview.dart | 2 +- lib/widgets/viewer/overlay/top.dart | 3 +- .../viewer/overlay/video/controls.dart | 8 +- .../viewer/overlay/video/progress_bar.dart | 2 +- lib/widgets/viewer/overlay/video/video.dart | 5 +- .../viewer/overlay/viewer_buttons.dart | 6 +- .../viewer/overlay/wallpaper_buttons.dart | 5 +- lib/widgets/viewer/page_entry_builder.dart | 2 +- lib/widgets/viewer/panorama_page.dart | 4 +- lib/widgets/viewer/slideshow_page.dart | 2 +- lib/widgets/viewer/video/conductor.dart | 16 +- .../video/db_playback_state_handler.dart | 58 ++ lib/widgets/viewer/video/fijkplayer.dart | 15 +- .../viewer/video/flutter_vlc_player.dart | 2 +- lib/widgets/viewer/video/video_player.dart | 2 +- lib/widgets/viewer/visual/conductor.dart | 3 +- .../viewer/visual/controller_mixin.dart | 6 +- .../viewer/visual/entry_page_view.dart | 5 +- lib/widgets/viewer/visual/error.dart | 2 +- lib/widgets/viewer/visual/raster.dart | 5 +- lib/widgets/viewer/visual/vector.dart | 5 +- lib/widgets/viewer/visual/video/cover.dart | 7 +- .../visual/video/subtitle/subtitle.dart | 8 +- .../viewer/visual/video/video_view.dart | 4 +- lib/widgets/wallpaper_page.dart | 33 +- plugins/aves_model/.gitignore | 30 + plugins/aves_model/.metadata | 10 + plugins/aves_model/analysis_options.yaml | 4 + plugins/aves_model/lib/aves_model.dart | 3 + plugins/aves_model/lib/src/entry_base.dart | 23 + plugins/aves_model/pubspec.lock | 79 +++ plugins/aves_model/pubspec.yaml | 15 + plugins/aves_video/.gitignore | 30 + plugins/aves_video/.metadata | 10 + plugins/aves_video/analysis_options.yaml | 1 + plugins/aves_video/lib/aves_video.dart | 3 + .../aves_video/lib/src}/controller.dart | 75 +-- plugins/aves_video/pubspec.lock | 94 +++ plugins/aves_video/pubspec.yaml | 17 + pubspec.lock | 14 + pubspec.yaml | 4 + test/fake/media_fetch_service.dart | 2 +- test/fake/media_store_service.dart | 3 +- test/fake/metadata_db.dart | 2 +- test/fake/metadata_fetch_service.dart | 2 +- test/model/collection_source_test.dart | 1 + test/utils/xmp_utils_test.dart | 2 +- 191 files changed, 2051 insertions(+), 677 deletions(-) rename lib/model/{entry_cache.dart => entry/cache.dart} (100%) rename lib/model/{entry_dirs.dart => entry/dirs.dart} (100%) rename lib/model/{ => entry}/entry.dart (55%) create mode 100644 lib/model/entry/extensions/catalog.dart create mode 100644 lib/model/entry/extensions/favourites.dart create mode 100644 lib/model/entry/extensions/images.dart create mode 100644 lib/model/entry/extensions/info.dart create mode 100644 lib/model/entry/extensions/location.dart create mode 100644 lib/model/entry/extensions/metadata_edition.dart create mode 100644 lib/model/entry/extensions/multipage.dart create mode 100644 lib/model/entry/extensions/props.dart create mode 100644 lib/model/entry/origins.dart create mode 100644 lib/model/entry/sort.dart create mode 100644 lib/model/entry_extensions/geo.dart rename lib/model/{entry_images.dart => entry_extensions/images.dart} (100%) rename lib/model/{entry_info.dart => entry_extensions/info.dart} (100%) rename lib/model/{entry_metadata_edition.dart => entry_extensions/metadata_edition.dart} (100%) create mode 100644 lib/model/entry_extensions/multipage.dart create mode 100644 lib/widgets/viewer/video/db_playback_state_handler.dart create mode 100644 plugins/aves_model/.gitignore create mode 100644 plugins/aves_model/.metadata create mode 100644 plugins/aves_model/analysis_options.yaml create mode 100644 plugins/aves_model/lib/aves_model.dart create mode 100644 plugins/aves_model/lib/src/entry_base.dart create mode 100644 plugins/aves_model/pubspec.lock create mode 100644 plugins/aves_model/pubspec.yaml create mode 100644 plugins/aves_video/.gitignore create mode 100644 plugins/aves_video/.metadata create mode 100644 plugins/aves_video/analysis_options.yaml create mode 100644 plugins/aves_video/lib/aves_video.dart rename {lib/widgets/viewer/video => plugins/aves_video/lib/src}/controller.dart (52%) create mode 100644 plugins/aves_video/pubspec.lock create mode 100644 plugins/aves_video/pubspec.yaml diff --git a/lib/model/covers.dart b/lib/model/covers.dart index 601a07bd5..0eb13eb55 100644 --- a/lib/model/covers.dart +++ b/lib/model/covers.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/vaults/vaults.dart'; diff --git a/lib/model/db/db_metadata.dart b/lib/model/db/db_metadata.dart index bf4f677ae..524efd299 100644 --- a/lib/model/db/db_metadata.dart +++ b/lib/model/db/db_metadata.dart @@ -1,5 +1,5 @@ import 'package:aves/model/covers.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/favourites.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/metadata/address.dart'; diff --git a/lib/model/db/db_metadata_sqflite.dart b/lib/model/db/db_metadata_sqflite.dart index 9bc6a8885..e8903a076 100644 --- a/lib/model/db/db_metadata_sqflite.dart +++ b/lib/model/db/db_metadata_sqflite.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'package:aves/model/covers.dart'; import 'package:aves/model/db/db_metadata.dart'; import 'package:aves/model/db/db_metadata_sqflite_upgrade.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/favourites.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/metadata/address.dart'; diff --git a/lib/model/entry_cache.dart b/lib/model/entry/cache.dart similarity index 100% rename from lib/model/entry_cache.dart rename to lib/model/entry/cache.dart diff --git a/lib/model/entry_dirs.dart b/lib/model/entry/dirs.dart similarity index 100% rename from lib/model/entry_dirs.dart rename to lib/model/entry/dirs.dart diff --git a/lib/model/entry.dart b/lib/model/entry/entry.dart similarity index 55% rename from lib/model/entry.dart rename to lib/model/entry/entry.dart index 794ed6f6e..cfa81b453 100644 --- a/lib/model/entry.dart +++ b/lib/model/entry/entry.dart @@ -2,51 +2,42 @@ import 'dart:async'; import 'dart:io'; import 'dart:ui'; -import 'package:aves/geo/countries.dart'; -import 'package:aves/model/entry_cache.dart'; -import 'package:aves/model/entry_dirs.dart'; -import 'package:aves/model/favourites.dart'; -import 'package:aves/model/geotiff.dart'; +import 'package:aves/model/entry/cache.dart'; +import 'package:aves/model/entry/dirs.dart'; import 'package:aves/model/metadata/address.dart'; import 'package:aves/model/metadata/catalog.dart'; import 'package:aves/model/metadata/trash.dart'; -import 'package:aves/model/multipage.dart'; -import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/trash.dart'; -import 'package:aves/model/video/metadata.dart'; import 'package:aves/ref/mime_types.dart'; -import 'package:aves/services/common/service_policy.dart'; import 'package:aves/services/common/services.dart'; -import 'package:aves/services/geocoding_service.dart'; -import 'package:aves/services/metadata/svg_metadata_service.dart'; import 'package:aves/theme/format.dart'; -import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/utils/change_notifier.dart'; import 'package:aves/utils/time_utils.dart'; +import 'package:aves_model/aves_model.dart'; import 'package:collection/collection.dart'; -import 'package:country_code/country_code.dart'; import 'package:flutter/foundation.dart'; -import 'package:latlong2/latlong.dart'; enum EntryDataType { basic, aspectRatio, catalog, address, references } -class EntryOrigins { - static const int mediaStoreContent = 0; - static const int unknownContent = 1; - static const int file = 2; - static const int vault = 3; -} - -class AvesEntry { - // `sizeBytes`, `dateModifiedSecs` can be missing in viewer mode +class AvesEntry with AvesEntryBase { + @override int id; + + @override String uri; + + @override + int? pageId; + + @override + int? sizeBytes; + String? _path, _filename, _extension, _sourceTitle; EntryDir? _directory; - int? pageId, contentId; + int? contentId; final String sourceMimeType; int width, height, sourceRotationDegrees; - int? sizeBytes, dateAddedSecs, _dateModifiedSecs, sourceDateTakenMillis, _durationMillis; + int? dateAddedSecs, _dateModifiedSecs, sourceDateTakenMillis, _durationMillis; bool trashed; int origin; @@ -57,7 +48,11 @@ class AvesEntry { List? burstEntries; - final AChangeNotifier visualChangeNotifier = AChangeNotifier(), metadataChangeNotifier = AChangeNotifier(), addressChangeNotifier = AChangeNotifier(); + @override + final AChangeNotifier visualChangeNotifier = AChangeNotifier(); + + final AChangeNotifier metadataChangeNotifier = AChangeNotifier(); + final AChangeNotifier addressChangeNotifier = AChangeNotifier(); AvesEntry({ required int? id, @@ -243,140 +238,8 @@ class AvesEntry { // so we use the one found during cataloguing if possible String get mimeType => _catalogMetadata?.mimeType ?? sourceMimeType; - String get mimeTypeAnySubtype => mimeType.replaceAll(RegExp('/.*'), '/*'); - - bool get isFavourite => favourites.isFavourite(this); - - bool get isSvg => mimeType == MimeTypes.svg; - - // guess whether this is a photo, according to file type (used as a hint to e.g. display megapixels) - bool get isPhoto => [MimeTypes.heic, MimeTypes.heif, MimeTypes.jpeg, MimeTypes.tiff].contains(mimeType) || isRaw; - - // Android's `BitmapRegionDecoder` documentation states that "only the JPEG and PNG formats are supported" - // but in practice (tested on API 25, 27, 29), it successfully decodes the formats listed below, - // and it actually fails to decode GIF, DNG and animated WEBP. Other formats were not tested. - bool get _supportedByBitmapRegionDecoder => - [ - MimeTypes.heic, - MimeTypes.heif, - MimeTypes.jpeg, - MimeTypes.png, - MimeTypes.webp, - MimeTypes.arw, - MimeTypes.cr2, - MimeTypes.nef, - MimeTypes.nrw, - MimeTypes.orf, - MimeTypes.pef, - MimeTypes.raf, - MimeTypes.rw2, - MimeTypes.srw, - ].contains(mimeType) && - !isAnimated; - - bool get supportTiling => _supportedByBitmapRegionDecoder || mimeType == MimeTypes.tiff; - - bool get useTiles => supportTiling && (width > 4096 || height > 4096); - - bool get isRaw => MimeTypes.rawImages.contains(mimeType); - - bool get isImage => MimeTypes.isImage(mimeType); - - bool get isVideo => MimeTypes.isVideo(mimeType); - bool get isCatalogued => _catalogMetadata != null; - bool get isAnimated => _catalogMetadata?.isAnimated ?? false; - - bool get isGeotiff => _catalogMetadata?.isGeotiff ?? false; - - bool get is360 => _catalogMetadata?.is360 ?? false; - - bool get isMediaStoreContent => uri.startsWith('content://media/'); - - bool get isMediaStoreMediaContent => isMediaStoreContent && {'/external/images/', '/external/video/'}.any(uri.contains); - - bool get isVaultContent => path?.startsWith(androidFileUtils.vaultRoot) ?? false; - - bool get canEdit => !settings.isReadOnly && path != null && !trashed && (isMediaStoreContent || isVaultContent); - - bool get canEditDate => canEdit && (canEditExif || canEditXmp); - - bool get canEditLocation => canEdit && (canEditExif || mimeType == MimeTypes.mp4); - - bool get canEditTitleDescription => canEdit && canEditXmp; - - bool get canEditRating => canEdit && canEditXmp; - - bool get canEditTags => canEdit && canEditXmp; - - bool get canRotate => canEdit && (canEditExif || mimeType == MimeTypes.mp4); - - bool get canFlip => canEdit && canEditExif; - - bool get canEditExif => MimeTypes.canEditExif(mimeType); - - bool get canEditIptc => MimeTypes.canEditIptc(mimeType); - - bool get canEditXmp => MimeTypes.canEditXmp(mimeType); - - bool get canRemoveMetadata => MimeTypes.canRemoveMetadata(mimeType); - - // Media Store size/rotation is inaccurate, e.g. a portrait FHD video is rotated according to its metadata, - // so it should be registered as width=1920, height=1080, orientation=90, - // but is incorrectly registered as width=1080, height=1920, orientation=0. - // Double-checking the width/height during loading or cataloguing is the proper solution, but it would take space and time. - // Comparing width and height can help with the portrait FHD video example, - // but it fails for a portrait screenshot rotated, which is landscape with width=1080, height=1920, orientation=90 - bool get isRotated => rotationDegrees % 180 == 90; - - static const ratioSeparator = '\u2236'; - static const resolutionSeparator = ' \u00D7 '; - - bool get isSized => width > 0 && height > 0; - - String get resolutionText { - final ws = width; - final hs = height; - return isRotated ? '$hs$resolutionSeparator$ws' : '$ws$resolutionSeparator$hs'; - } - - String get aspectRatioText { - if (width > 0 && height > 0) { - final gcd = width.gcd(height); - final w = width ~/ gcd; - final h = height ~/ gcd; - return isRotated ? '$h$ratioSeparator$w' : '$w$ratioSeparator$h'; - } else { - return '?$ratioSeparator?'; - } - } - - double get displayAspectRatio { - if (width == 0 || height == 0) return 1; - return isRotated ? height / width : width / height; - } - - Size get displaySize { - final w = width.toDouble(); - final h = height.toDouble(); - return isRotated ? Size(h, w) : Size(w, h); - } - - Size videoDisplaySize(double sar) { - final size = displaySize; - if (sar != 1) { - final dar = displayAspectRatio * sar; - final w = size.width; - final h = size.height; - if (w >= h) return Size(w, w / dar); - if (h > w) return Size(h * dar, h); - } - return size; - } - - int get megaPixels => (width * height / 1000000).round(); - DateTime? _bestDate; DateTime? get bestDate { @@ -386,6 +249,7 @@ class AvesEntry { int get rating => _catalogMetadata?.rating ?? 0; + @override int get rotationDegrees => _catalogMetadata?.rotationDegrees ?? sourceRotationDegrees; set rotationDegrees(int rotationDegrees) { @@ -397,6 +261,27 @@ class AvesEntry { set isFlipped(bool isFlipped) => _catalogMetadata?.isFlipped = isFlipped; + // Media Store size/rotation is inaccurate, e.g. a portrait FHD video is rotated according to its metadata, + // so it should be registered as width=1920, height=1080, orientation=90, + // but is incorrectly registered as width=1080, height=1920, orientation=0. + // Double-checking the width/height during loading or cataloguing is the proper solution, but it would take space and time. + // Comparing width and height can help with the portrait FHD video example, + // but it fails for a portrait screenshot rotated, which is landscape with width=1080, height=1920, orientation=90 + bool get isRotated => rotationDegrees % 180 == 90; + + @override + double get displayAspectRatio { + if (width == 0 || height == 0) return 1; + return isRotated ? height / width : width / height; + } + + @override + Size get displaySize { + final w = width.toDouble(); + final h = height.toDouble(); + return isRotated ? Size(h, w) : Size(w, h); + } + String? get sourceTitle => _sourceTitle; set sourceTitle(String? sourceTitle) { @@ -423,6 +308,7 @@ class AvesEntry { return d == null ? null : DateTime(d.year, d.month, d.day); } + @override int? get durationMillis => _durationMillis; set durationMillis(int? durationMillis) { @@ -459,8 +345,6 @@ class AvesEntry { // derived from Google reverse geocoding addresses bool get hasFineAddress => _addressDetails?.place?.isNotEmpty == true || (_addressDetails?.countryName?.length ?? 0) > 3; - LatLng? get latLng => hasGps ? LatLng(_catalogMetadata!.latitude!, _catalogMetadata!.longitude!) : null; - Set? _tags; Set get tags { @@ -504,53 +388,6 @@ class AvesEntry { addressDetails = null; } - Future catalog({required bool background, required bool force, required bool persist}) async { - if (isCatalogued && !force) return; - if (isSvg) { - // vector image sizing is not essential, so we should not spend time for it during loading - // but it is useful anyway (for aspect ratios etc.) so we size them during cataloguing - final size = await SvgMetadataService.getSize(this); - if (size != null) { - final fields = { - 'width': size.width.ceil(), - 'height': size.height.ceil(), - }; - await applyNewFields(fields, persist: persist); - } - catalogMetadata = CatalogMetadata(id: id); - } else { - // pre-processing - if (isVideo && (!isSized || durationMillis == 0)) { - // exotic video that is not sized during loading - final fields = await VideoMetadataFormatter.getLoadingMetadata(this); - await applyNewFields(fields, persist: persist); - } - - // cataloguing on platform - catalogMetadata = await metadataFetchService.getCatalogMetadata(this, background: background); - - // post-processing - if (isVideo && (catalogMetadata?.dateMillis ?? 0) == 0) { - catalogMetadata = await VideoMetadataFormatter.getCatalogMetadata(this); - } - if (isGeotiff && !hasGps) { - final info = await metadataFetchService.getGeoTiffInfo(this); - if (info != null) { - final center = MappedGeoTiff( - info: info, - entry: this, - ).center; - if (center != null) { - catalogMetadata = catalogMetadata?.copyWith( - latitude: center.latitude, - longitude: center.longitude, - ); - } - } - } - } - } - AddressDetails? get addressDetails => _addressDetails; set addressDetails(AddressDetails? newAddress) { @@ -558,79 +395,6 @@ class AvesEntry { addressChangeNotifier.notify(); } - Future locate({required bool background, required bool force, required Locale geocoderLocale}) async { - if (hasGps) { - await _locateCountry(force: force); - if (await availability.canLocatePlaces) { - await locatePlace(background: background, force: force, geocoderLocale: geocoderLocale); - } - } else { - addressDetails = null; - } - } - - // quick reverse geocoding to find the country, using an offline asset - Future _locateCountry({required bool force}) async { - if (!hasGps || (hasAddress && !force)) return; - final countryCode = await countryTopology.countryCode(latLng!); - setCountry(countryCode); - } - - void setCountry(CountryCode? countryCode) { - if (hasFineAddress || countryCode == null) return; - addressDetails = AddressDetails( - id: id, - countryCode: countryCode.alpha2, - countryName: countryCode.alpha3, - ); - } - - // full reverse geocoding, requiring Play Services and some connectivity - Future locatePlace({required bool background, required bool force, required Locale geocoderLocale}) async { - if (!hasGps || (hasFineAddress && !force)) return; - try { - Future> call() => GeocodingService.getAddress(latLng!, geocoderLocale); - final addresses = await (background - ? servicePolicy.call( - call, - priority: ServiceCallPriority.getLocation, - ) - : call()); - if (addresses.isNotEmpty) { - final address = addresses.first; - final cc = address.countryCode?.toUpperCase(); - final cn = address.countryName; - final aa = address.adminArea; - addressDetails = AddressDetails( - id: id, - countryCode: cc, - countryName: cn, - adminArea: aa, - // if country & admin fields are null, it is likely the ocean, - // which is identified by `featureName` but we default to the address line anyway - locality: address.locality ?? (cc == null && cn == null && aa == null ? address.addressLine : null), - ); - } - } catch (error, stack) { - debugPrint('$runtimeType locate failed with path=$path coordinates=$latLng error=$error\n$stack'); - } - } - - Future findAddressLine({required Locale geocoderLocale}) async { - if (!hasGps) return null; - - try { - final addresses = await GeocodingService.getAddress(latLng!, geocoderLocale); - if (addresses.isNotEmpty) { - final address = addresses.first; - return address.addressLine; - } - } catch (error, stack) { - debugPrint('$runtimeType findAddressLine failed with path=$path coordinates=$latLng error=$error\n$stack'); - } - return null; - } - String get shortAddress { // `admin area` examples: Seoul, Geneva, null // `locality` examples: Mapo-gu, Geneva, Annecy @@ -732,107 +496,4 @@ class AvesEntry { visualChangeNotifier.notify(); } } - - // favourites - - Future toggleFavourite() async { - if (isFavourite) { - await removeFromFavourites(); - } else { - await addToFavourites(); - } - } - - Future addToFavourites() async { - if (!isFavourite) { - await favourites.add({this}); - } - } - - Future removeFromFavourites() async { - if (isFavourite) { - await favourites.removeEntries({this}); - } - } - - // multipage - - static final _burstFilenamePattern = RegExp(r'^(\d{8}_\d{6})_(\d+)$'); - - bool get isMultiPage => (_catalogMetadata?.isMultiPage ?? false) || isBurst; - - bool get isBurst => burstEntries?.isNotEmpty == true; - - // for backward compatibility - bool get _isMotionPhotoLegacy => isMultiPage && !isBurst && mimeType == MimeTypes.jpeg; - - bool get isMotionPhoto => (_catalogMetadata?.isMotionPhoto ?? false) || _isMotionPhotoLegacy; - - String? get burstKey { - if (filenameWithoutExtension != null) { - final match = _burstFilenamePattern.firstMatch(filenameWithoutExtension!); - if (match != null) { - return '$directory/${match.group(1)}'; - } - } - return null; - } - - Future getMultiPageInfo() async { - if (isBurst) { - return MultiPageInfo( - mainEntry: this, - pages: burstEntries! - .mapIndexed((index, entry) => SinglePageInfo( - index: index, - pageId: entry.id, - isDefault: index == 0, - uri: entry.uri, - mimeType: entry.mimeType, - width: entry.width, - height: entry.height, - rotationDegrees: entry.rotationDegrees, - durationMillis: entry.durationMillis, - )) - .toList(), - ); - } else { - return await metadataFetchService.getMultiPageInfo(this); - } - } - - // sort - - // compare by: - // 1) title ascending - // 2) extension ascending - static int compareByName(AvesEntry a, AvesEntry b) { - final c = compareAsciiUpperCaseNatural(a.bestTitle ?? '', b.bestTitle ?? ''); - return c != 0 ? c : compareAsciiUpperCase(a.extension ?? '', b.extension ?? ''); - } - - // compare by: - // 1) date descending - // 2) name descending - static int compareByDate(AvesEntry a, AvesEntry b) { - var c = (b.bestDate ?? epoch).compareTo(a.bestDate ?? epoch); - if (c != 0) return c; - return compareByName(b, a); - } - - // compare by: - // 1) rating descending - // 2) date descending - static int compareByRating(AvesEntry a, AvesEntry b) { - final c = b.rating.compareTo(a.rating); - return c != 0 ? c : compareByDate(a, b); - } - - // compare by: - // 1) size descending - // 2) date descending - static int compareBySize(AvesEntry a, AvesEntry b) { - final c = (b.sizeBytes ?? 0).compareTo(a.sizeBytes ?? 0); - return c != 0 ? c : compareByDate(a, b); - } } diff --git a/lib/model/entry/extensions/catalog.dart b/lib/model/entry/extensions/catalog.dart new file mode 100644 index 000000000..897f017a8 --- /dev/null +++ b/lib/model/entry/extensions/catalog.dart @@ -0,0 +1,57 @@ +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/props.dart'; +import 'package:aves/model/geotiff.dart'; +import 'package:aves/model/metadata/catalog.dart'; +import 'package:aves/model/video/metadata.dart'; +import 'package:aves/services/common/services.dart'; +import 'package:aves/services/metadata/svg_metadata_service.dart'; + +// TODO TLAD [split] need props +extension ExtraAvesEntryCatalog on AvesEntry { + Future catalog({required bool background, required bool force, required bool persist}) async { + if (isCatalogued && !force) return; + if (isSvg) { + // vector image sizing is not essential, so we should not spend time for it during loading + // but it is useful anyway (for aspect ratios etc.) so we size them during cataloguing + final size = await SvgMetadataService.getSize(this); + if (size != null) { + final fields = { + 'width': size.width.ceil(), + 'height': size.height.ceil(), + }; + await applyNewFields(fields, persist: persist); + } + catalogMetadata = CatalogMetadata(id: id); + } else { + // pre-processing + if (isVideo && (!isSized || durationMillis == 0)) { + // exotic video that is not sized during loading + final fields = await VideoMetadataFormatter.getLoadingMetadata(this); + await applyNewFields(fields, persist: persist); + } + + // cataloguing on platform + catalogMetadata = await metadataFetchService.getCatalogMetadata(this, background: background); + + // post-processing + if (isVideo && (catalogMetadata?.dateMillis ?? 0) == 0) { + catalogMetadata = await VideoMetadataFormatter.getCatalogMetadata(this); + } + if (isGeotiff && !hasGps) { + final info = await metadataFetchService.getGeoTiffInfo(this); + if (info != null) { + final center = MappedGeoTiff( + info: info, + entry: this, + ).center; + if (center != null) { + catalogMetadata = catalogMetadata?.copyWith( + latitude: center.latitude, + longitude: center.longitude, + ); + } + } + } + } + } +} diff --git a/lib/model/entry/extensions/favourites.dart b/lib/model/entry/extensions/favourites.dart new file mode 100644 index 000000000..22ca36f0b --- /dev/null +++ b/lib/model/entry/extensions/favourites.dart @@ -0,0 +1,26 @@ +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/favourites.dart'; + +extension ExtraAvesEntryFav on AvesEntry { + bool get isFavourite => favourites.isFavourite(this); + + Future toggleFavourite() async { + if (isFavourite) { + await removeFromFavourites(); + } else { + await addToFavourites(); + } + } + + Future addToFavourites() async { + if (!isFavourite) { + await favourites.add({this}); + } + } + + Future removeFromFavourites() async { + if (isFavourite) { + await favourites.removeEntries({this}); + } + } +} diff --git a/lib/model/entry/extensions/images.dart b/lib/model/entry/extensions/images.dart new file mode 100644 index 000000000..46e18a337 --- /dev/null +++ b/lib/model/entry/extensions/images.dart @@ -0,0 +1,79 @@ +import 'dart:math'; + +import 'package:aves/image_providers/region_provider.dart'; +import 'package:aves/image_providers/thumbnail_provider.dart'; +import 'package:aves/image_providers/uri_image_provider.dart'; +import 'package:aves/model/entry/cache.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/utils/math_utils.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/widgets.dart'; + +extension ExtraAvesEntryImages on AvesEntry { + bool isThumbnailReady({double extent = 0}) => _isReady(_getThumbnailProviderKey(extent)); + + ThumbnailProvider getThumbnail({double extent = 0}) { + return ThumbnailProvider(_getThumbnailProviderKey(extent)); + } + + ThumbnailProviderKey _getThumbnailProviderKey(double extent) { + EntryCache.markThumbnailExtent(extent); + return ThumbnailProviderKey( + uri: uri, + mimeType: mimeType, + pageId: pageId, + rotationDegrees: rotationDegrees, + isFlipped: isFlipped, + dateModifiedSecs: dateModifiedSecs ?? -1, + extent: extent, + ); + } + + RegionProvider getRegion({int sampleSize = 1, double scale = 1, required Rectangle region}) { + return RegionProvider(RegionProviderKey( + uri: uri, + mimeType: mimeType, + pageId: pageId, + sizeBytes: sizeBytes, + rotationDegrees: rotationDegrees, + isFlipped: isFlipped, + sampleSize: sampleSize, + region: Rectangle( + (region.left * scale).round(), + (region.top * scale).round(), + (region.width * scale).round(), + (region.height * scale).round(), + ), + imageSize: Size((width * scale).toDouble(), (height * scale).toDouble()), + )); + } + + UriImage get uriImage => UriImage( + uri: uri, + mimeType: mimeType, + pageId: pageId, + rotationDegrees: rotationDegrees, + isFlipped: isFlipped, + sizeBytes: sizeBytes, + ); + + bool _isReady(Object providerKey) => imageCache.statusForKey(providerKey).keepAlive; + + List get cachedThumbnails => EntryCache.thumbnailRequestExtents.map(_getThumbnailProviderKey).where(_isReady).map(ThumbnailProvider.new).toList(); + + ThumbnailProvider get bestCachedThumbnail { + final sizedThumbnailKey = EntryCache.thumbnailRequestExtents.map(_getThumbnailProviderKey).firstWhereOrNull(_isReady); + return sizedThumbnailKey != null ? ThumbnailProvider(sizedThumbnailKey) : getThumbnail(); + } + + // magic number used to derive sample size from scale + static const scaleFactor = 2.0; + + static int sampleSizeForScale(double scale) { + var sample = 0; + if (0 < scale && scale < 1) { + sample = highestPowerOf2((1 / scale) / scaleFactor); + } + return max(1, sample); + } +} diff --git a/lib/model/entry/extensions/info.dart b/lib/model/entry/extensions/info.dart new file mode 100644 index 000000000..4087d10ac --- /dev/null +++ b/lib/model/entry/extensions/info.dart @@ -0,0 +1,161 @@ +import 'dart:async'; +import 'dart:collection'; + +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/multipage.dart'; +import 'package:aves/model/entry/extensions/props.dart'; +import 'package:aves/model/video/keys.dart'; +import 'package:aves/model/video/metadata.dart'; +import 'package:aves/ref/mime_types.dart'; +import 'package:aves/services/common/services.dart'; +import 'package:aves/services/metadata/svg_metadata_service.dart'; +import 'package:aves/theme/colors.dart'; +import 'package:aves/utils/constants.dart'; +import 'package:aves/widgets/viewer/info/metadata/metadata_dir.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +// TODO TLAD [split] need props/multipage +extension ExtraAvesEntryInfo on AvesEntry { + // directory names may contain the name of their parent directory (as prefix + '/') + // directory names may contain an index (as suffix in '[]') + static final directoryNamePattern = RegExp(r'^((?.*?)/)?(?.*?)(\[(?\d+)\])?$'); + + Future>> getMetadataDirectories(BuildContext context) async { + final rawMetadata = await (isSvg ? SvgMetadataService.getAllMetadata(this) : metadataFetchService.getAllMetadata(this)); + final directories = rawMetadata.entries.map((dirKV) { + var directoryName = dirKV.key as String; + + String? parent; + int? index; + final match = directoryNamePattern.firstMatch(directoryName); + if (match != null) { + parent = match.namedGroup('parent'); + final nameMatch = match.namedGroup('name'); + if (nameMatch != null) { + directoryName = nameMatch; + } + final indexMatch = match.namedGroup('index'); + if (indexMatch != null) { + index = int.tryParse(indexMatch); + } + } + + final rawTags = dirKV.value as Map; + return MetadataDirectory( + directoryName, + _toSortedTags(rawTags), + parent: parent, + index: index, + ); + }).toList(); + + if (isVideo || (mimeType == MimeTypes.heif && isMultiPage)) { + directories.addAll(await _getStreamDirectories(context)); + } + + final titledDirectories = directories.map((dir) { + var title = dir.name; + if (directories.where((dir) => dir.name == title).length > 1 && dir.parent?.isNotEmpty == true) { + title = '${dir.parent}/$title'; + } + if (dir.index != null) { + title += ' ${dir.index}'; + } + return MapEntry(title, dir); + }).toList() + ..sort((a, b) => compareAsciiUpperCase(a.key, b.key)); + + return titledDirectories; + } + + Future> _getStreamDirectories(BuildContext context) async { + final directories = []; + final mediaInfo = await VideoMetadataFormatter.getVideoMetadata(this); + + final formattedMediaTags = VideoMetadataFormatter.formatInfo(mediaInfo); + if (formattedMediaTags.isNotEmpty) { + // overwrite generic directory found from the platform side + directories.add(MetadataDirectory(MetadataDirectory.mediaDirectory, _toSortedTags(formattedMediaTags))); + } + + if (mediaInfo.containsKey(Keys.streams)) { + String getTypeText(Map stream) { + final type = stream[Keys.streamType] ?? StreamTypes.unknown; + switch (type) { + case StreamTypes.attachment: + return 'Attachment'; + case StreamTypes.audio: + return 'Audio'; + case StreamTypes.metadata: + return 'Metadata'; + case StreamTypes.subtitle: + case StreamTypes.timedText: + return 'Text'; + case StreamTypes.video: + return stream.containsKey(Keys.fpsDen) ? 'Video' : 'Image'; + case StreamTypes.unknown: + default: + return 'Unknown'; + } + } + + final allStreams = (mediaInfo[Keys.streams] as List).cast(); + final attachmentStreams = allStreams.where((stream) => stream[Keys.streamType] == StreamTypes.attachment).toList(); + final knownStreams = allStreams.whereNot(attachmentStreams.contains); + + // display known streams as separate directories (e.g. video, audio, subs) + if (knownStreams.isNotEmpty) { + final indexDigits = knownStreams.length.toString().length; + + final colors = context.read(); + for (final stream in knownStreams) { + final index = (stream[Keys.index] ?? 0) + 1; + final typeText = getTypeText(stream); + final dirName = [ + 'Stream ${index.toString().padLeft(indexDigits, '0')}', + typeText, + ].join(Constants.separator); + final formattedStreamTags = VideoMetadataFormatter.formatInfo(stream); + if (formattedStreamTags.isNotEmpty) { + final color = colors.fromString(typeText); + directories.add(MetadataDirectory(dirName, _toSortedTags(formattedStreamTags), color: color)); + } + } + } + + // group attachments by format (e.g. TTF fonts) + if (attachmentStreams.isNotEmpty) { + final formatCount = >{}; + for (final stream in attachmentStreams) { + final codec = (stream[Keys.codecName] as String? ?? 'unknown').toUpperCase(); + if (!formatCount.containsKey(codec)) { + formatCount[codec] = []; + } + formatCount[codec]!.add(stream[Keys.filename]); + } + if (formatCount.isNotEmpty) { + final rawTags = formatCount.map((key, value) { + final count = value.length; + // remove duplicate names, so number of displayed names may not match displayed count + final names = value.whereNotNull().toSet().toList()..sort(compareAsciiUpperCase); + return MapEntry(key, '$count items: ${names.join(', ')}'); + }); + directories.add(MetadataDirectory('Attachments', _toSortedTags(rawTags))); + } + } + } + return directories; + } + + SplayTreeMap _toSortedTags(Map rawTags) { + final tags = SplayTreeMap.of(Map.fromEntries(rawTags.entries.map((tagKV) { + var value = (tagKV.value as String? ?? '').trim(); + if (value.isEmpty) return null; + final tagName = tagKV.key as String; + return MapEntry(tagName, value); + }).whereNotNull())); + return tags; + } +} diff --git a/lib/model/entry/extensions/location.dart b/lib/model/entry/extensions/location.dart new file mode 100644 index 000000000..97f477c15 --- /dev/null +++ b/lib/model/entry/extensions/location.dart @@ -0,0 +1,89 @@ +import 'dart:async'; +import 'dart:ui'; + +import 'package:aves/geo/countries.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/metadata/address.dart'; +import 'package:aves/services/common/service_policy.dart'; +import 'package:aves/services/common/services.dart'; +import 'package:aves/services/geocoding_service.dart'; +import 'package:country_code/country_code.dart'; +import 'package:flutter/foundation.dart'; +import 'package:latlong2/latlong.dart'; + +extension ExtraAvesEntryLocation on AvesEntry { + LatLng? get latLng => hasGps ? LatLng(catalogMetadata!.latitude!, catalogMetadata!.longitude!) : null; + + Future locate({required bool background, required bool force, required Locale geocoderLocale}) async { + if (hasGps) { + await _locateCountry(force: force); + if (await availability.canLocatePlaces) { + await locatePlace(background: background, force: force, geocoderLocale: geocoderLocale); + } + } else { + addressDetails = null; + } + } + + // quick reverse geocoding to find the country, using an offline asset + Future _locateCountry({required bool force}) async { + if (!hasGps || (hasAddress && !force)) return; + final countryCode = await countryTopology.countryCode(latLng!); + setCountry(countryCode); + } + + void setCountry(CountryCode? countryCode) { + if (hasFineAddress || countryCode == null) return; + addressDetails = AddressDetails( + id: id, + countryCode: countryCode.alpha2, + countryName: countryCode.alpha3, + ); + } + + // full reverse geocoding, requiring Play Services and some connectivity + Future locatePlace({required bool background, required bool force, required Locale geocoderLocale}) async { + if (!hasGps || (hasFineAddress && !force)) return; + try { + Future> call() => GeocodingService.getAddress(latLng!, geocoderLocale); + final addresses = await (background + ? servicePolicy.call( + call, + priority: ServiceCallPriority.getLocation, + ) + : call()); + if (addresses.isNotEmpty) { + final address = addresses.first; + final cc = address.countryCode?.toUpperCase(); + final cn = address.countryName; + final aa = address.adminArea; + addressDetails = AddressDetails( + id: id, + countryCode: cc, + countryName: cn, + adminArea: aa, + // if country & admin fields are null, it is likely the ocean, + // which is identified by `featureName` but we default to the address line anyway + locality: address.locality ?? (cc == null && cn == null && aa == null ? address.addressLine : null), + ); + } + } catch (error, stack) { + debugPrint('$runtimeType locate failed with path=$path coordinates=$latLng error=$error\n$stack'); + } + } + + Future findAddressLine({required Locale geocoderLocale}) async { + if (!hasGps) return null; + + try { + final addresses = await GeocodingService.getAddress(latLng!, geocoderLocale); + if (addresses.isNotEmpty) { + final address = addresses.first; + return address.addressLine; + } + } catch (error, stack) { + debugPrint('$runtimeType findAddressLine failed with path=$path coordinates=$latLng error=$error\n$stack'); + } + return null; + } +} diff --git a/lib/model/entry/extensions/metadata_edition.dart b/lib/model/entry/extensions/metadata_edition.dart new file mode 100644 index 000000000..c283b873f --- /dev/null +++ b/lib/model/entry/extensions/metadata_edition.dart @@ -0,0 +1,590 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/catalog.dart'; +import 'package:aves/model/entry/extensions/props.dart'; +import 'package:aves/model/metadata/date_modifier.dart'; +import 'package:aves/model/metadata/enums/date_field_source.dart'; +import 'package:aves/model/metadata/enums/enums.dart'; +import 'package:aves/model/metadata/fields.dart'; +import 'package:aves/ref/exif.dart'; +import 'package:aves/ref/iptc.dart'; +import 'package:aves/ref/mime_types.dart'; +import 'package:aves/services/common/services.dart'; +import 'package:aves/services/metadata/xmp.dart'; +import 'package:aves/utils/time_utils.dart'; +import 'package:aves/utils/xmp_utils.dart'; +import 'package:flutter/foundation.dart'; +import 'package:intl/intl.dart'; +import 'package:latlong2/latlong.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:xml/xml.dart'; + +// TODO TLAD [split] need props/catalog +extension ExtraAvesEntryMetadataEdition on AvesEntry { + Future> editDate(DateModifier userModifier) async { + final dataTypes = {}; + + final appliedModifier = await _applyDateModifierToEntry(userModifier); + if (appliedModifier == null) { + if (!isMissingAtPath && userModifier.action != DateEditAction.copyField) { + await reportService.recordError('failed to get date for modifier=$userModifier, entry=$this', null); + } + return {}; + } + + if (canEditExif && appliedModifier.fields.any((v) => v.type == MetadataType.exif)) { + final newFields = await metadataEditService.editExifDate(this, appliedModifier); + if (newFields.isNotEmpty) { + dataTypes.addAll({ + EntryDataType.basic, + EntryDataType.catalog, + }); + } + } + + if (canEditXmp && appliedModifier.fields.any((v) => v.type == MetadataType.xmp)) { + final metadata = { + MetadataType.xmp: await _editXmp((descriptions) { + switch (appliedModifier.action) { + case DateEditAction.setCustom: + case DateEditAction.copyField: + case DateEditAction.copyItem: + case DateEditAction.extractFromTitle: + editCreateDateXmp(descriptions, appliedModifier.setDateTime); + break; + case DateEditAction.shift: + final xmpDate = XMP.getString(descriptions, XMP.xmpCreateDate, namespace: Namespaces.xmp); + if (xmpDate != null) { + final date = DateTime.tryParse(xmpDate); + if (date != null) { + // TODO TLAD [date] DateTime.tryParse converts to UTC time, losing the time zone offset + final shiftedDate = date.add(Duration(minutes: appliedModifier.shiftMinutes!)); + editCreateDateXmp(descriptions, shiftedDate); + } else { + reportService.recordError('failed to parse XMP date=$xmpDate', null); + } + } + break; + case DateEditAction.remove: + editCreateDateXmp(descriptions, null); + break; + } + return true; + }), + }; + final newFields = await metadataEditService.editMetadata(this, metadata); + if (newFields.isNotEmpty) { + dataTypes.addAll({ + EntryDataType.basic, + EntryDataType.catalog, + }); + } + } + + return dataTypes; + } + + static final removalLocation = LatLng(0, 0); + + Future> editLocation(LatLng? latLng) async { + final dataTypes = {}; + final metadata = {}; + + final missingDate = await _missingDateCheckAndExifEdit(dataTypes); + + if (canEditExif) { + // clear every GPS field + final exifFields = Map.fromEntries(MetadataFields.exifGpsFields.map((k) => MapEntry(k, null))); + // add latitude & longitude, if any + if (latLng != null && latLng != removalLocation) { + final latitude = latLng.latitude; + final longitude = latLng.longitude; + exifFields.addAll({ + MetadataField.exifGpsLatitude: latitude.abs(), + MetadataField.exifGpsLatitudeRef: latitude >= 0 ? Exif.latitudeNorth : Exif.latitudeSouth, + MetadataField.exifGpsLongitude: longitude.abs(), + MetadataField.exifGpsLongitudeRef: longitude >= 0 ? Exif.longitudeEast : Exif.longitudeWest, + }); + } + metadata[MetadataType.exif] = Map.fromEntries(exifFields.entries.map((kv) => MapEntry(kv.key.toPlatform!, kv.value))); + + if (canEditXmp && missingDate != null) { + metadata[MetadataType.xmp] = await _editXmp((descriptions) { + editCreateDateXmp(descriptions, missingDate); + return true; + }); + } + } + + if (mimeType == MimeTypes.mp4) { + final mp4Fields = {}; + + String? iso6709String; + if (latLng != null && latLng != removalLocation) { + final latitude = latLng.latitude; + final longitude = latLng.longitude; + const locale = 'en_US'; + final isoLat = '${latitude >= 0 ? '+' : '-'}${NumberFormat('00.0000', locale).format(latitude.abs())}'; + final isoLon = '${longitude >= 0 ? '+' : '-'}${NumberFormat('000.0000', locale).format(longitude.abs())}'; + iso6709String = '$isoLat$isoLon/'; + } + mp4Fields[MetadataField.mp4GpsCoordinates] = iso6709String; + + if (missingDate != null) { + final xmpParts = await _editXmp((descriptions) { + editCreateDateXmp(descriptions, missingDate); + return true; + }); + mp4Fields[MetadataField.mp4Xmp] = xmpParts[xmpCoreKey]; + } + + metadata[MetadataType.mp4] = Map.fromEntries(mp4Fields.entries.map((kv) => MapEntry(kv.key.toPlatform!, kv.value))); + } + + final newFields = await metadataEditService.editMetadata(this, metadata); + if (newFields.isNotEmpty) { + dataTypes.addAll({ + EntryDataType.catalog, + EntryDataType.address, + }); + } + return dataTypes; + } + + Future> _changeExifOrientation(Future> Function() apply) async { + final dataTypes = {}; + + await _missingDateCheckAndExifEdit(dataTypes); + + final newFields = await apply(); + // applying fields is only useful for a smoother visual change, + // as proper refreshing and persistence happens at the caller level + await applyNewFields(newFields, persist: false); + if (newFields.isNotEmpty) { + dataTypes.addAll({ + EntryDataType.basic, + EntryDataType.aspectRatio, + EntryDataType.catalog, + }); + } + return dataTypes; + } + + Future> _rotateMp4(int rotationDegrees) async { + final dataTypes = {}; + + final missingDate = await _missingDateCheckAndExifEdit(dataTypes); + + final mp4Fields = { + MetadataField.mp4RotationDegrees: rotationDegrees.toString(), + }; + + if (missingDate != null) { + final xmpParts = await _editXmp((descriptions) { + editCreateDateXmp(descriptions, missingDate); + return true; + }); + mp4Fields[MetadataField.mp4Xmp] = xmpParts[xmpCoreKey]; + } + + final metadata = { + MetadataType.mp4: Map.fromEntries(mp4Fields.entries.map((kv) => MapEntry(kv.key.toPlatform!, kv.value))), + }; + + final newFields = await metadataEditService.editMetadata(this, metadata); + // applying fields is only useful for a smoother visual change, + // as proper refreshing and persistence happens at the caller level + await applyNewFields(newFields, persist: false); + if (newFields.isNotEmpty) { + dataTypes.addAll({ + EntryDataType.basic, + EntryDataType.aspectRatio, + EntryDataType.catalog, + }); + } + return dataTypes; + } + + Future> rotate({required bool clockwise}) { + if (mimeType == MimeTypes.mp4) { + return _rotateMp4((rotationDegrees + (clockwise ? 90 : -90) + 360) % 360); + } else { + return _changeExifOrientation(() => metadataEditService.rotate(this, clockwise: clockwise)); + } + } + + Future> flip() { + return _changeExifOrientation(() => metadataEditService.flip(this)); + } + + // write title: + // - IPTC / object-name, if IPTC exists + // - XMP / dc:title + // write description: + // - Exif / ImageDescription + // - IPTC / caption-abstract, if IPTC exists + // - XMP / dc:description + Future> editTitleDescription(Map fields) async { + final dataTypes = {}; + final metadata = {}; + + final missingDate = await _missingDateCheckAndExifEdit(dataTypes); + + final editTitle = fields.keys.contains(DescriptionField.title); + final editDescription = fields.keys.contains(DescriptionField.description); + final title = fields[DescriptionField.title]; + final description = fields[DescriptionField.description]; + + if (canEditExif && editDescription) { + metadata[MetadataType.exif] = { + MetadataField.exifImageDescription.toPlatform!: null, + MetadataField.exifUserComment.toPlatform!: null, + }; + } + + if (canEditIptc) { + final iptc = await metadataFetchService.getIptc(this); + if (iptc != null) { + if (editTitle) { + editIptcValues(iptc, IPTC.applicationRecord, IPTC.objectName, {if (title != null) title}); + } + if (editDescription) { + editIptcValues(iptc, IPTC.applicationRecord, IPTC.captionAbstractTag, {if (description != null) description}); + } + metadata[MetadataType.iptc] = iptc; + } + } + + if (canEditXmp) { + metadata[MetadataType.xmp] = await _editXmp((descriptions) { + var modified = false; + if (editTitle) { + modified |= XMP.setAttribute( + descriptions, + XMP.dcTitle, + title, + namespace: Namespaces.dc, + strat: XmpEditStrategy.always, + ); + } + if (editDescription) { + modified |= XMP.setAttribute( + descriptions, + XMP.dcDescription, + description, + namespace: Namespaces.dc, + strat: XmpEditStrategy.always, + ); + } + if (modified && missingDate != null) { + editCreateDateXmp(descriptions, missingDate); + } + return modified; + }); + } + + final newFields = await metadataEditService.editMetadata(this, metadata); + if (newFields.isNotEmpty) { + dataTypes.addAll({ + EntryDataType.basic, + EntryDataType.catalog, + }); + } + + return dataTypes; + } + + // write: + // - IPTC / keywords, if IPTC exists + // - XMP / dc:subject + Future> editTags(Set tags) async { + final dataTypes = {}; + final metadata = {}; + + final missingDate = await _missingDateCheckAndExifEdit(dataTypes); + + if (canEditIptc) { + final iptc = await metadataFetchService.getIptc(this); + if (iptc != null) { + editIptcValues(iptc, IPTC.applicationRecord, IPTC.keywordsTag, tags); + metadata[MetadataType.iptc] = iptc; + } + } + + if (canEditXmp) { + metadata[MetadataType.xmp] = await _editXmp((descriptions) { + final modified = editTagsXmp(descriptions, tags); + if (modified && missingDate != null) { + editCreateDateXmp(descriptions, missingDate); + } + return modified; + }); + } + + final newFields = await metadataEditService.editMetadata(this, metadata); + if (newFields.isNotEmpty) { + dataTypes.add(EntryDataType.catalog); + } + return dataTypes; + } + + // write: + // - XMP / xmp:Rating + // update: + // - XMP / MicrosoftPhoto:Rating + // ignore (Windows tags, not part of Exif 2.32 spec): + // - Exif / Rating + // - Exif / RatingPercent + Future> editRating(int? rating) async { + final dataTypes = {}; + final metadata = {}; + + final missingDate = await _missingDateCheckAndExifEdit(dataTypes); + + if (canEditXmp) { + metadata[MetadataType.xmp] = await _editXmp((descriptions) { + final modified = editRatingXmp(descriptions, rating); + if (modified && missingDate != null) { + editCreateDateXmp(descriptions, missingDate); + } + return modified; + }); + } + + final newFields = await metadataEditService.editMetadata(this, metadata); + if (newFields.isNotEmpty) { + dataTypes.add(EntryDataType.catalog); + } + return dataTypes; + } + + // remove: + // - trailer video + // - XMP / Container:Directory + // - XMP / GCamera:MicroVideo* + // - XMP / GCamera:MotionPhoto* + Future> removeTrailerVideo() async { + final dataTypes = {}; + final metadata = {}; + + if (!canEditXmp) return dataTypes; + + final missingDate = await _missingDateCheckAndExifEdit(dataTypes); + + final newFields = await metadataEditService.removeTrailerVideo(this); + + metadata[MetadataType.xmp] = await _editXmp((descriptions) { + final modified = removeContainerXmp(descriptions); + if (modified && missingDate != null) { + editCreateDateXmp(descriptions, missingDate); + } + return modified; + }); + + newFields.addAll(await metadataEditService.editMetadata(this, metadata, autoCorrectTrailerOffset: false)); + if (newFields.isNotEmpty) { + dataTypes.add(EntryDataType.catalog); + } + return dataTypes; + } + + Future> removeMetadata(Set types) async { + final dataTypes = {}; + + final newFields = await metadataEditService.removeTypes(this, types); + if (newFields.isNotEmpty) { + dataTypes.addAll({ + EntryDataType.basic, + EntryDataType.aspectRatio, + EntryDataType.catalog, + EntryDataType.address, + }); + } + return dataTypes; + } + + static void editIptcValues(List> iptc, int record, int tag, Set values) { + iptc.removeWhere((v) => v['record'] == record && v['tag'] == tag); + iptc.add({ + 'record': record, + 'tag': tag, + 'values': values.map((v) => utf8.encode(v)).toList(), + }); + } + + @visibleForTesting + static bool editCreateDateXmp(List descriptions, DateTime? date) { + return XMP.setAttribute( + descriptions, + XMP.xmpCreateDate, + date != null ? XMP.toXmpDate(date) : null, + namespace: Namespaces.xmp, + strat: XmpEditStrategy.always, + ); + } + + @visibleForTesting + static bool editTagsXmp(List descriptions, Set tags) { + return XMP.setStringBag( + descriptions, + XMP.dcSubject, + tags, + namespace: Namespaces.dc, + strat: XmpEditStrategy.always, + ); + } + + @visibleForTesting + static bool editRatingXmp(List descriptions, int? rating) { + bool modified = false; + + modified |= XMP.setAttribute( + descriptions, + XMP.xmpRating, + (rating ?? 0) == 0 ? null : '$rating', + namespace: Namespaces.xmp, + strat: XmpEditStrategy.always, + ); + + modified |= XMP.setAttribute( + descriptions, + XMP.msPhotoRating, + XMP.toMsPhotoRating(rating), + namespace: Namespaces.microsoftPhoto, + strat: XmpEditStrategy.updateIfPresent, + ); + + return modified; + } + + @visibleForTesting + static bool removeContainerXmp(List descriptions) { + bool modified = false; + + modified |= XMP.removeElements( + descriptions, + XMP.containerDirectory, + Namespaces.gContainer, + ); + + modified |= [ + XMP.gCameraMicroVideo, + XMP.gCameraMicroVideoVersion, + XMP.gCameraMicroVideoOffset, + XMP.gCameraMicroVideoPresentationTimestampUs, + XMP.gCameraMotionPhoto, + XMP.gCameraMotionPhotoVersion, + XMP.gCameraMotionPhotoPresentationTimestampUs, + ].fold(modified, (prev, name) { + return prev |= XMP.removeElements( + descriptions, + name, + Namespaces.gCamera, + ); + }); + + return modified; + } + + // convenience methods + + // This method checks whether the item already has a metadata date, + // and adds a date (the file modified date) via Exif if possible. + // It returns a date if the caller needs to add it via other metadata types (e.g. XMP). + Future _missingDateCheckAndExifEdit(Set dataTypes) async { + if (path == null) return null; + + // make sure entry is catalogued before we check whether is has a metadata date + if (!isCatalogued) { + await catalog(background: false, force: false, persist: true); + } + final dateMillis = catalogMetadata?.dateMillis; + if (dateMillis != null && dateMillis > 0) return null; + + late DateTime date; + try { + date = await File(path!).lastModified(); + } on FileSystemException catch (_) { + return null; + } + + if (canEditExif) { + final newFields = await metadataEditService.editExifDate(this, DateModifier.setCustom(const {MetadataField.exifDateOriginal}, date)); + if (newFields.isNotEmpty) { + dataTypes.addAll({ + EntryDataType.basic, + EntryDataType.catalog, + }); + return null; + } + } + + return date; + } + + Future _applyDateModifierToEntry(DateModifier modifier) async { + Set mainMetadataDate() => {canEditExif ? MetadataField.exifDateOriginal : MetadataField.xmpXmpCreateDate}; + + switch (modifier.action) { + case DateEditAction.copyField: + DateTime? date; + final source = modifier.copyFieldSource; + if (source != null) { + switch (source) { + case DateFieldSource.fileModifiedDate: + try { + if (path != null) { + final file = File(path!); + if (await file.exists()) { + date = await file.lastModified(); + } + } + } on FileSystemException catch (_) {} + break; + default: + date = await metadataFetchService.getDate(this, source.toMetadataField()!); + break; + } + } + return date != null ? DateModifier.setCustom(mainMetadataDate(), date) : null; + case DateEditAction.extractFromTitle: + final date = parseUnknownDateFormat(bestTitle); + return date != null ? DateModifier.setCustom(mainMetadataDate(), date) : null; + case DateEditAction.setCustom: + case DateEditAction.copyItem: + return DateModifier.setCustom(mainMetadataDate(), modifier.setDateTime!); + case DateEditAction.shift: + case DateEditAction.remove: + return modifier; + } + } + + static const xmpCoreKey = 'xmp'; + static const xmpExtendedKey = 'extendedXmp'; + + Future> _editXmp(bool Function(List descriptions) apply) async { + final xmp = await metadataFetchService.getXmp(this); + if (xmp == null) { + throw Exception('failed to get XMP'); + } + + final xmpString = xmp.xmpString; + final extendedXmpString = xmp.extendedXmpString; + + final editedXmpString = await XMP.edit( + xmpString, + () => PackageInfo.fromPlatform().then((v) => 'Aves v${v.version}'), + apply, + ); + + final editedXmp = AvesXmp(xmpString: editedXmpString, extendedXmpString: extendedXmpString); + return { + xmpCoreKey: editedXmp.xmpString, + xmpExtendedKey: editedXmp.extendedXmpString, + }; + } +} + +enum DescriptionField { title, description } diff --git a/lib/model/entry/extensions/multipage.dart b/lib/model/entry/extensions/multipage.dart new file mode 100644 index 000000000..f77f252b1 --- /dev/null +++ b/lib/model/entry/extensions/multipage.dart @@ -0,0 +1,53 @@ +import 'dart:async'; + +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/multipage.dart'; +import 'package:aves/ref/mime_types.dart'; +import 'package:aves/services/common/services.dart'; +import 'package:collection/collection.dart'; + +extension ExtraAvesEntryMultipage on AvesEntry { + static final _burstFilenamePattern = RegExp(r'^(\d{8}_\d{6})_(\d+)$'); + + bool get isMultiPage => (catalogMetadata?.isMultiPage ?? false) || isBurst; + + bool get isBurst => burstEntries?.isNotEmpty == true; + + // for backward compatibility + bool get _isMotionPhotoLegacy => isMultiPage && !isBurst && mimeType == MimeTypes.jpeg; + + bool get isMotionPhoto => (catalogMetadata?.isMotionPhoto ?? false) || _isMotionPhotoLegacy; + + String? get burstKey { + if (filenameWithoutExtension != null) { + final match = _burstFilenamePattern.firstMatch(filenameWithoutExtension!); + if (match != null) { + return '$directory/${match.group(1)}'; + } + } + return null; + } + + Future getMultiPageInfo() async { + if (isBurst) { + return MultiPageInfo( + mainEntry: this, + pages: burstEntries! + .mapIndexed((index, entry) => SinglePageInfo( + index: index, + pageId: entry.id, + isDefault: index == 0, + uri: entry.uri, + mimeType: entry.mimeType, + width: entry.width, + height: entry.height, + rotationDegrees: entry.rotationDegrees, + durationMillis: entry.durationMillis, + )) + .toList(), + ); + } else { + return await metadataFetchService.getMultiPageInfo(this); + } + } +} diff --git a/lib/model/entry/extensions/props.dart b/lib/model/entry/extensions/props.dart new file mode 100644 index 000000000..ed1c3ef1c --- /dev/null +++ b/lib/model/entry/extensions/props.dart @@ -0,0 +1,119 @@ +import 'dart:ui'; + +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/settings/settings.dart'; +import 'package:aves/ref/mime_types.dart'; +import 'package:aves/utils/android_file_utils.dart'; + +extension ExtraAvesEntryProps on AvesEntry { + String get mimeTypeAnySubtype => mimeType.replaceAll(RegExp('/.*'), '/*'); + + bool get isSvg => mimeType == MimeTypes.svg; + + // guess whether this is a photo, according to file type (used as a hint to e.g. display megapixels) + bool get isPhoto => [MimeTypes.heic, MimeTypes.heif, MimeTypes.jpeg, MimeTypes.tiff].contains(mimeType) || isRaw; + + // Android's `BitmapRegionDecoder` documentation states that "only the JPEG and PNG formats are supported" + // but in practice (tested on API 25, 27, 29), it successfully decodes the formats listed below, + // and it actually fails to decode GIF, DNG and animated WEBP. Other formats were not tested. + bool get _supportedByBitmapRegionDecoder => + [ + MimeTypes.heic, + MimeTypes.heif, + MimeTypes.jpeg, + MimeTypes.png, + MimeTypes.webp, + MimeTypes.arw, + MimeTypes.cr2, + MimeTypes.nef, + MimeTypes.nrw, + MimeTypes.orf, + MimeTypes.pef, + MimeTypes.raf, + MimeTypes.rw2, + MimeTypes.srw, + ].contains(mimeType) && + !isAnimated; + + bool get supportTiling => _supportedByBitmapRegionDecoder || mimeType == MimeTypes.tiff; + + bool get useTiles => supportTiling && (width > 4096 || height > 4096); + + bool get isRaw => MimeTypes.rawImages.contains(mimeType); + + bool get isImage => MimeTypes.isImage(mimeType); + + bool get isVideo => MimeTypes.isVideo(mimeType); + + bool get isAnimated => catalogMetadata?.isAnimated ?? false; + + bool get isGeotiff => catalogMetadata?.isGeotiff ?? false; + + bool get is360 => catalogMetadata?.is360 ?? false; + + bool get isMediaStoreContent => uri.startsWith('content://media/'); + + bool get isMediaStoreMediaContent => isMediaStoreContent && {'/external/images/', '/external/video/'}.any(uri.contains); + + bool get isVaultContent => path?.startsWith(androidFileUtils.vaultRoot) ?? false; + + bool get canEdit => !settings.isReadOnly && path != null && !trashed && (isMediaStoreContent || isVaultContent); + + bool get canEditDate => canEdit && (canEditExif || canEditXmp); + + bool get canEditLocation => canEdit && (canEditExif || mimeType == MimeTypes.mp4); + + bool get canEditTitleDescription => canEdit && canEditXmp; + + bool get canEditRating => canEdit && canEditXmp; + + bool get canEditTags => canEdit && canEditXmp; + + bool get canRotate => canEdit && (canEditExif || mimeType == MimeTypes.mp4); + + bool get canFlip => canEdit && canEditExif; + + bool get canEditExif => MimeTypes.canEditExif(mimeType); + + bool get canEditIptc => MimeTypes.canEditIptc(mimeType); + + bool get canEditXmp => MimeTypes.canEditXmp(mimeType); + + bool get canRemoveMetadata => MimeTypes.canRemoveMetadata(mimeType); + + static const ratioSeparator = '\u2236'; + static const resolutionSeparator = ' \u00D7 '; + + bool get isSized => width > 0 && height > 0; + + String get resolutionText { + final ws = width; + final hs = height; + return isRotated ? '$hs$resolutionSeparator$ws' : '$ws$resolutionSeparator$hs'; + } + + String get aspectRatioText { + if (width > 0 && height > 0) { + final gcd = width.gcd(height); + final w = width ~/ gcd; + final h = height ~/ gcd; + return isRotated ? '$h$ratioSeparator$w' : '$w$ratioSeparator$h'; + } else { + return '?$ratioSeparator?'; + } + } + + Size videoDisplaySize(double sar) { + final size = displaySize; + if (sar != 1) { + final dar = displayAspectRatio * sar; + final w = size.width; + final h = size.height; + if (w >= h) return Size(w, w / dar); + if (h > w) return Size(h * dar, h); + } + return size; + } + + int get megaPixels => (width * height / 1000000).round(); +} diff --git a/lib/model/entry/origins.dart b/lib/model/entry/origins.dart new file mode 100644 index 000000000..efc35496a --- /dev/null +++ b/lib/model/entry/origins.dart @@ -0,0 +1,6 @@ +class EntryOrigins { + static const int mediaStoreContent = 0; + static const int unknownContent = 1; + static const int file = 2; + static const int vault = 3; +} diff --git a/lib/model/entry/sort.dart b/lib/model/entry/sort.dart new file mode 100644 index 000000000..f15521bc0 --- /dev/null +++ b/lib/model/entry/sort.dart @@ -0,0 +1,38 @@ +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/utils/time_utils.dart'; +import 'package:collection/collection.dart'; + +class AvesEntrySort { + // compare by: + // 1) title ascending + // 2) extension ascending + static int compareByName(AvesEntry a, AvesEntry b) { + final c = compareAsciiUpperCaseNatural(a.bestTitle ?? '', b.bestTitle ?? ''); + return c != 0 ? c : compareAsciiUpperCase(a.extension ?? '', b.extension ?? ''); + } + + // compare by: + // 1) date descending + // 2) name descending + static int compareByDate(AvesEntry a, AvesEntry b) { + var c = (b.bestDate ?? epoch).compareTo(a.bestDate ?? epoch); + if (c != 0) return c; + return compareByName(b, a); + } + + // compare by: + // 1) rating descending + // 2) date descending + static int compareByRating(AvesEntry a, AvesEntry b) { + final c = b.rating.compareTo(a.rating); + return c != 0 ? c : compareByDate(a, b); + } + + // compare by: + // 1) size descending + // 2) date descending + static int compareBySize(AvesEntry a, AvesEntry b) { + final c = (b.sizeBytes ?? 0).compareTo(a.sizeBytes ?? 0); + return c != 0 ? c : compareByDate(a, b); + } +} diff --git a/lib/model/entry_extensions/geo.dart b/lib/model/entry_extensions/geo.dart new file mode 100644 index 000000000..e69de29bb diff --git a/lib/model/entry_images.dart b/lib/model/entry_extensions/images.dart similarity index 100% rename from lib/model/entry_images.dart rename to lib/model/entry_extensions/images.dart diff --git a/lib/model/entry_info.dart b/lib/model/entry_extensions/info.dart similarity index 100% rename from lib/model/entry_info.dart rename to lib/model/entry_extensions/info.dart diff --git a/lib/model/entry_metadata_edition.dart b/lib/model/entry_extensions/metadata_edition.dart similarity index 100% rename from lib/model/entry_metadata_edition.dart rename to lib/model/entry_extensions/metadata_edition.dart diff --git a/lib/model/entry_extensions/multipage.dart b/lib/model/entry_extensions/multipage.dart new file mode 100644 index 000000000..e69de29bb diff --git a/lib/model/favourites.dart b/lib/model/favourites.dart index d994e31cc..8c5eeea57 100644 --- a/lib/model/favourites.dart +++ b/lib/model/favourites.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/utils/android_file_utils.dart'; diff --git a/lib/model/filters/aspect_ratio.dart b/lib/model/filters/aspect_ratio.dart index 92ee97279..a9a86c90b 100644 --- a/lib/model/filters/aspect_ratio.dart +++ b/lib/model/filters/aspect_ratio.dart @@ -1,3 +1,4 @@ +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/query.dart'; import 'package:aves/theme/icons.dart'; diff --git a/lib/model/filters/coordinate.dart b/lib/model/filters/coordinate.dart index 704d6bac3..2c822fecf 100644 --- a/lib/model/filters/coordinate.dart +++ b/lib/model/filters/coordinate.dart @@ -1,4 +1,5 @@ import 'package:aves/l10n/l10n.dart'; +import 'package:aves/model/entry/extensions/location.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/enums/coordinate_format.dart'; import 'package:aves/model/settings/enums/enums.dart'; diff --git a/lib/model/filters/favourite.dart b/lib/model/filters/favourite.dart index 7a028411c..413da4c86 100644 --- a/lib/model/filters/favourite.dart +++ b/lib/model/filters/favourite.dart @@ -1,4 +1,5 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/favourites.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/theme/colors.dart'; import 'package:aves/theme/icons.dart'; diff --git a/lib/model/filters/filters.dart b/lib/model/filters/filters.dart index a043214b8..212b6c92d 100644 --- a/lib/model/filters/filters.dart +++ b/lib/model/filters/filters.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'package:aves/model/covers.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/aspect_ratio.dart'; import 'package:aves/model/filters/coordinate.dart'; diff --git a/lib/model/filters/placeholder.dart b/lib/model/filters/placeholder.dart index e0be8e084..d2438e762 100644 --- a/lib/model/filters/placeholder.dart +++ b/lib/model/filters/placeholder.dart @@ -1,4 +1,6 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/catalog.dart'; +import 'package:aves/model/entry/extensions/location.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/icons.dart'; diff --git a/lib/model/filters/query.dart b/lib/model/filters/query.dart index b82a2ab1c..bf865cb07 100644 --- a/lib/model/filters/query.dart +++ b/lib/model/filters/query.dart @@ -1,4 +1,5 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/theme/colors.dart'; import 'package:aves/theme/icons.dart'; diff --git a/lib/model/filters/trash.dart b/lib/model/filters/trash.dart index a378a8668..c995f95b2 100644 --- a/lib/model/filters/trash.dart +++ b/lib/model/filters/trash.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; diff --git a/lib/model/filters/type.dart b/lib/model/filters/type.dart index 3ecb278f7..9eafa93b3 100644 --- a/lib/model/filters/type.dart +++ b/lib/model/filters/type.dart @@ -1,3 +1,5 @@ +import 'package:aves/model/entry/extensions/multipage.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/theme/colors.dart'; import 'package:aves/theme/icons.dart'; diff --git a/lib/model/geotiff.dart b/lib/model/geotiff.dart index dc3ab8deb..a557039ae 100644 --- a/lib/model/geotiff.dart +++ b/lib/model/geotiff.dart @@ -2,8 +2,8 @@ import 'dart:async'; import 'dart:math'; import 'dart:ui' as ui; -import 'package:aves/model/entry.dart'; -import 'package:aves/model/entry_images.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/images.dart'; import 'package:aves/ref/geotiff.dart'; import 'package:aves/utils/math_utils.dart'; import 'package:aves_map/aves_map.dart'; diff --git a/lib/model/multipage.dart b/lib/model/multipage.dart index b28945784..278a62e6a 100644 --- a/lib/model/multipage.dart +++ b/lib/model/multipage.dart @@ -1,4 +1,5 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/multipage.dart'; import 'package:aves/ref/mime_types.dart'; import 'package:aves/services/common/services.dart'; import 'package:collection/collection.dart'; diff --git a/lib/model/naming_pattern.dart b/lib/model/naming_pattern.dart index 2cbe98dfc..e24e5221f 100644 --- a/lib/model/naming_pattern.dart +++ b/lib/model/naming_pattern.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; diff --git a/lib/model/settings/enums/video_loop_mode.dart b/lib/model/settings/enums/video_loop_mode.dart index 0a6cba0a9..255e9ccde 100644 --- a/lib/model/settings/enums/video_loop_mode.dart +++ b/lib/model/settings/enums/video_loop_mode.dart @@ -1,4 +1,3 @@ -import 'package:aves/model/entry.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:flutter/widgets.dart'; @@ -18,12 +17,11 @@ extension ExtraVideoLoopMode on VideoLoopMode { static const shortVideoThreshold = Duration(seconds: 30); - bool shouldLoop(AvesEntry entry) { + bool shouldLoop(int? durationMillis) { switch (this) { case VideoLoopMode.never: return false; case VideoLoopMode.shortOnly: - final durationMillis = entry.durationMillis; return durationMillis != null ? durationMillis < shortVideoThreshold.inMilliseconds : false; case VideoLoopMode.always: return true; diff --git a/lib/model/settings/enums/widget_shape.dart b/lib/model/settings/enums/widget_shape.dart index 19374bfed..7bac91601 100644 --- a/lib/model/settings/enums/widget_shape.dart +++ b/lib/model/settings/enums/widget_shape.dart @@ -1,4 +1,5 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/settings/enums/enums.dart'; import 'package:flutter/material.dart'; diff --git a/lib/model/source/album.dart b/lib/model/source/album.dart index fae997994..7b4e9c48c 100644 --- a/lib/model/source/album.dart +++ b/lib/model/source/album.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; diff --git a/lib/model/source/collection_lens.dart b/lib/model/source/collection_lens.dart index 5c9730444..194236e83 100644 --- a/lib/model/source/collection_lens.dart +++ b/lib/model/source/collection_lens.dart @@ -2,7 +2,9 @@ import 'dart:async'; import 'dart:collection'; import 'package:aves/model/actions/move_type.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/multipage.dart'; +import 'package:aves/model/entry/sort.dart'; import 'package:aves/model/favourites.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/favourite.dart'; @@ -190,7 +192,7 @@ class CollectionLens with ChangeNotifier { final byBurstKey = groupBy(_filteredSortedEntries, (entry) => entry.burstKey).whereNotNullKey(); byBurstKey.forEach((burstKey, entries) { if (entries.length > 1) { - entries.sort(AvesEntry.compareByName); + entries.sort(AvesEntrySort.compareByName); final mainEntry = entries.first; final burstEntry = mainEntry.copyWith(burstEntries: entries); @@ -209,16 +211,16 @@ class CollectionLens with ChangeNotifier { switch (sortFactor) { case EntrySortFactor.date: - _filteredSortedEntries.sort(AvesEntry.compareByDate); + _filteredSortedEntries.sort(AvesEntrySort.compareByDate); break; case EntrySortFactor.name: - _filteredSortedEntries.sort(AvesEntry.compareByName); + _filteredSortedEntries.sort(AvesEntrySort.compareByName); break; case EntrySortFactor.rating: - _filteredSortedEntries.sort(AvesEntry.compareByRating); + _filteredSortedEntries.sort(AvesEntrySort.compareByRating); break; case EntrySortFactor.size: - _filteredSortedEntries.sort(AvesEntry.compareBySize); + _filteredSortedEntries.sort(AvesEntrySort.compareBySize); break; } if (sortReverse) { diff --git a/lib/model/source/collection_source.dart b/lib/model/source/collection_source.dart index e31296c46..e2925ac74 100644 --- a/lib/model/source/collection_source.dart +++ b/lib/model/source/collection_source.dart @@ -2,7 +2,10 @@ import 'dart:async'; import 'package:aves/model/actions/move_type.dart'; import 'package:aves/model/covers.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/catalog.dart'; +import 'package:aves/model/entry/extensions/location.dart'; +import 'package:aves/model/entry/sort.dart'; import 'package:aves/model/favourites.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/filters.dart'; @@ -105,7 +108,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place @override List get sortedEntriesByDate { - _sortedEntriesByDate ??= List.unmodifiable(visibleEntries.toList()..sort(AvesEntry.compareByDate)); + _sortedEntriesByDate ??= List.unmodifiable(visibleEntries.toList()..sort(AvesEntrySort.compareByDate)); return _sortedEntriesByDate!; } diff --git a/lib/model/source/events.dart b/lib/model/source/events.dart index eed59df63..2cd779f31 100644 --- a/lib/model/source/events.dart +++ b/lib/model/source/events.dart @@ -1,5 +1,5 @@ import 'package:aves/model/actions/move_type.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:flutter/foundation.dart'; @immutable diff --git a/lib/model/source/location/country.dart b/lib/model/source/location/country.dart index a6de50f6a..2fcce4db0 100644 --- a/lib/model/source/location/country.dart +++ b/lib/model/source/location/country.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/filters/location.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/utils/collection_utils.dart'; diff --git a/lib/model/source/location/location.dart b/lib/model/source/location/location.dart index 80703a5b6..75fec0ede 100644 --- a/lib/model/source/location/location.dart +++ b/lib/model/source/location/location.dart @@ -1,7 +1,8 @@ import 'dart:math'; import 'package:aves/geo/countries.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/location.dart'; import 'package:aves/model/filters/location.dart'; import 'package:aves/model/metadata/address.dart'; import 'package:aves/model/settings/settings.dart'; diff --git a/lib/model/source/location/place.dart b/lib/model/source/location/place.dart index 342202990..a0b1e3ab6 100644 --- a/lib/model/source/location/place.dart +++ b/lib/model/source/location/place.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/filters/location.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/utils/collection_utils.dart'; diff --git a/lib/model/source/media_store_source.dart b/lib/model/source/media_store_source.dart index 22595cd36..4e1c17f2d 100644 --- a/lib/model/source/media_store_source.dart +++ b/lib/model/source/media_store_source.dart @@ -2,7 +2,8 @@ import 'dart:async'; import 'dart:math'; import 'package:aves/model/covers.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/origins.dart'; import 'package:aves/model/favourites.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/analysis_controller.dart'; diff --git a/lib/model/source/tag.dart b/lib/model/source/tag.dart index e1ebd33e6..6b8dcd682 100644 --- a/lib/model/source/tag.dart +++ b/lib/model/source/tag.dart @@ -1,4 +1,5 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/catalog.dart'; import 'package:aves/model/filters/tag.dart'; import 'package:aves/model/metadata/catalog.dart'; import 'package:aves/model/source/analysis_controller.dart'; diff --git a/lib/model/video/metadata.dart b/lib/model/video/metadata.dart index 7245ca79b..73c38abae 100644 --- a/lib/model/video/metadata.dart +++ b/lib/model/video/metadata.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/metadata/catalog.dart'; import 'package:aves/model/video/channel_layouts.dart'; import 'package:aves/model/video/codecs.dart'; diff --git a/lib/services/android_app_service.dart b/lib/services/android_app_service.dart index 1db5487ad..cf181a68b 100644 --- a/lib/services/android_app_service.dart +++ b/lib/services/android_app_service.dart @@ -1,4 +1,5 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/utils/android_file_utils.dart'; diff --git a/lib/services/android_debug_service.dart b/lib/services/android_debug_service.dart index 29d2b8627..9acb772da 100644 --- a/lib/services/android_debug_service.dart +++ b/lib/services/android_debug_service.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/ref/mime_types.dart'; import 'package:aves/services/common/services.dart'; import 'package:flutter/services.dart'; diff --git a/lib/services/media/embedded_data_service.dart b/lib/services/media/embedded_data_service.dart index 33f024ba2..d4a668176 100644 --- a/lib/services/media/embedded_data_service.dart +++ b/lib/services/media/embedded_data_service.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/utils/constants.dart'; import 'package:flutter/services.dart'; diff --git a/lib/services/media/media_edit_service.dart b/lib/services/media/media_edit_service.dart index 6374a1941..0ef3ba684 100644 --- a/lib/services/media/media_edit_service.dart +++ b/lib/services/media/media_edit_service.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/metadata/enums/enums.dart'; import 'package:aves/services/common/image_op_events.dart'; import 'package:aves/services/common/services.dart'; diff --git a/lib/services/media/media_fetch_service.dart b/lib/services/media/media_fetch_service.dart index e50f161d8..4d6a9c4cd 100644 --- a/lib/services/media/media_fetch_service.dart +++ b/lib/services/media/media_fetch_service.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'dart:math'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/ref/mime_types.dart'; import 'package:aves/services/common/output_buffer.dart'; import 'package:aves/services/common/service_policy.dart'; diff --git a/lib/services/media/media_session_service.dart b/lib/services/media/media_session_service.dart index 643c19297..ed645eadb 100644 --- a/lib/services/media/media_session_service.dart +++ b/lib/services/media/media_session_service.dart @@ -1,8 +1,9 @@ import 'dart:async'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/services/common/optional_event_channel.dart'; import 'package:aves/services/common/services.dart'; -import 'package:aves/widgets/viewer/video/controller.dart'; +import 'package:aves_video/aves_video.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; @@ -12,6 +13,7 @@ abstract class MediaSessionService { Stream get mediaCommands; Future update({ + required AvesEntry entry, required AvesVideoController controller, required bool canSkipToNext, required bool canSkipToPrevious, @@ -43,11 +45,11 @@ class PlatformMediaSessionService implements MediaSessionService, Disposable { @override Future update({ + required AvesEntry entry, required AvesVideoController controller, required bool canSkipToNext, required bool canSkipToPrevious, }) async { - final entry = controller.entry; try { await _platformObject.invokeMethod('update', { 'uri': entry.uri, diff --git a/lib/services/media/media_store_service.dart b/lib/services/media/media_store_service.dart index 203fd2944..ab762d13a 100644 --- a/lib/services/media/media_store_service.dart +++ b/lib/services/media/media_store_service.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/services/common/services.dart'; import 'package:flutter/services.dart'; import 'package:streams_channel/streams_channel.dart'; diff --git a/lib/services/metadata/metadata_edit_service.dart b/lib/services/metadata/metadata_edit_service.dart index 9816fd9a2..53eb204a4 100644 --- a/lib/services/metadata/metadata_edit_service.dart +++ b/lib/services/metadata/metadata_edit_service.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/metadata/date_modifier.dart'; import 'package:aves/model/metadata/enums/enums.dart'; import 'package:aves/model/metadata/enums/metadata_type.dart'; diff --git a/lib/services/metadata/metadata_fetch_service.dart b/lib/services/metadata/metadata_fetch_service.dart index 16c52177f..4998a1b6c 100644 --- a/lib/services/metadata/metadata_fetch_service.dart +++ b/lib/services/metadata/metadata_fetch_service.dart @@ -1,4 +1,6 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/multipage.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/geotiff.dart'; import 'package:aves/model/metadata/catalog.dart'; import 'package:aves/model/metadata/fields.dart'; diff --git a/lib/services/metadata/svg_metadata_service.dart b/lib/services/metadata/svg_metadata_service.dart index a792ef028..882b3527b 100644 --- a/lib/services/metadata/svg_metadata_service.dart +++ b/lib/services/metadata/svg_metadata_service.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/utils/string_utils.dart'; import 'package:collection/collection.dart'; diff --git a/lib/widget_common.dart b/lib/widget_common.dart index e2316fb3f..c111a54b5 100644 --- a/lib/widget_common.dart +++ b/lib/widget_common.dart @@ -1,7 +1,8 @@ import 'dart:async'; import 'package:aves/app_flavor.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/sort.dart'; import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; @@ -80,7 +81,7 @@ Future _getWidgetEntry(int widgetId, bool reuseEntry) async { entries.shuffle(); break; case WidgetDisplayedItem.mostRecent: - entries.sort(AvesEntry.compareByDate); + entries.sort(AvesEntrySort.compareByDate); break; } final entry = entries.firstOrNull; diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart index d9206938a..de01c21f4 100644 --- a/lib/widgets/collection/app_bar.dart +++ b/lib/widgets/collection/app_bar.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:aves/app_mode.dart'; import 'package:aves/model/actions/entry_set_actions.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/query.dart'; import 'package:aves/model/filters/trash.dart'; diff --git a/lib/widgets/collection/collection_grid.dart b/lib/widgets/collection/collection_grid.dart index 15d424e05..f1d78f0b5 100644 --- a/lib/widgets/collection/collection_grid.dart +++ b/lib/widgets/collection/collection_grid.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:aves/app_mode.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/favourites.dart'; import 'package:aves/model/filters/favourite.dart'; import 'package:aves/model/filters/mime.dart'; diff --git a/lib/widgets/collection/collection_page.dart b/lib/widgets/collection/collection_page.dart index 0251638a3..0c3a1ac0e 100644 --- a/lib/widgets/collection/collection_page.dart +++ b/lib/widgets/collection/collection_page.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:aves/app_mode.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/query.dart'; import 'package:aves/model/filters/trash.dart'; diff --git a/lib/widgets/collection/draggable_thumb_label.dart b/lib/widgets/collection/draggable_thumb_label.dart index d04670818..6c5bf4bb6 100644 --- a/lib/widgets/collection/draggable_thumb_label.dart +++ b/lib/widgets/collection/draggable_thumb_label.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/filters/rating.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; diff --git a/lib/widgets/collection/entry_set_action_delegate.dart b/lib/widgets/collection/entry_set_action_delegate.dart index f6f0a6545..3f690f478 100644 --- a/lib/widgets/collection/entry_set_action_delegate.dart +++ b/lib/widgets/collection/entry_set_action_delegate.dart @@ -4,8 +4,10 @@ import 'package:aves/app_mode.dart'; import 'package:aves/model/actions/entry_set_actions.dart'; import 'package:aves/model/actions/move_type.dart'; import 'package:aves/model/device.dart'; -import 'package:aves/model/entry.dart'; -import 'package:aves/model/entry_metadata_edition.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/favourites.dart'; +import 'package:aves/model/entry/extensions/metadata_edition.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/favourites.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/metadata/date_modifier.dart'; diff --git a/lib/widgets/collection/grid/headers/album.dart b/lib/widgets/collection/grid/headers/album.dart index 85fb41b32..dc15e9bfc 100644 --- a/lib/widgets/collection/grid/headers/album.dart +++ b/lib/widgets/collection/grid/headers/album.dart @@ -1,5 +1,5 @@ import 'package:aves/model/covers.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/section_keys.dart'; import 'package:aves/theme/icons.dart'; diff --git a/lib/widgets/collection/grid/headers/any.dart b/lib/widgets/collection/grid/headers/any.dart index c3949add9..f3333bda7 100644 --- a/lib/widgets/collection/grid/headers/any.dart +++ b/lib/widgets/collection/grid/headers/any.dart @@ -1,6 +1,6 @@ import 'dart:math'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/enums/enums.dart'; diff --git a/lib/widgets/collection/grid/list_details.dart b/lib/widgets/collection/grid/list_details.dart index f71ad7d0b..456ffecc1 100644 --- a/lib/widgets/collection/grid/list_details.dart +++ b/lib/widgets/collection/grid/list_details.dart @@ -1,4 +1,5 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/location.dart'; import 'package:aves/model/settings/enums/coordinate_format.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/format.dart'; diff --git a/lib/widgets/collection/grid/section_layout.dart b/lib/widgets/collection/grid/section_layout.dart index 276861e32..09a8524f4 100644 --- a/lib/widgets/collection/grid/section_layout.dart +++ b/lib/widgets/collection/grid/section_layout.dart @@ -1,4 +1,5 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/section_keys.dart'; import 'package:aves/widgets/collection/grid/headers/any.dart'; diff --git a/lib/widgets/collection/grid/tile.dart b/lib/widgets/collection/grid/tile.dart index 64eddc2c0..38d5bc5b8 100644 --- a/lib/widgets/collection/grid/tile.dart +++ b/lib/widgets/collection/grid/tile.dart @@ -1,5 +1,5 @@ import 'package:aves/app_mode.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/selection.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/enums/enums.dart'; diff --git a/lib/widgets/collection/query_bar.dart b/lib/widgets/collection/query_bar.dart index d1442b4c3..e9bc1cc68 100644 --- a/lib/widgets/collection/query_bar.dart +++ b/lib/widgets/collection/query_bar.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/selection.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/widgets/common/basic/query_bar.dart'; diff --git a/lib/widgets/common/action_controls/quick_choosers/share_button.dart b/lib/widgets/common/action_controls/quick_choosers/share_button.dart index 379c6b9a2..7e0164400 100644 --- a/lib/widgets/common/action_controls/quick_choosers/share_button.dart +++ b/lib/widgets/common/action_controls/quick_choosers/share_button.dart @@ -1,6 +1,7 @@ import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/model/actions/share_actions.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/multipage.dart'; import 'package:aves/widgets/common/action_controls/quick_choosers/common/button.dart'; import 'package:aves/widgets/common/action_controls/quick_choosers/share_chooser.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/common/action_controls/togglers/favourite.dart b/lib/widgets/common/action_controls/togglers/favourite.dart index 510853037..4589b528e 100644 --- a/lib/widgets/common/action_controls/togglers/favourite.dart +++ b/lib/widgets/common/action_controls/togglers/favourite.dart @@ -1,4 +1,5 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/favourites.dart'; import 'package:aves/model/favourites.dart'; import 'package:aves/theme/colors.dart'; import 'package:aves/theme/icons.dart'; diff --git a/lib/widgets/common/action_controls/togglers/mute.dart b/lib/widgets/common/action_controls/togglers/mute.dart index 7eec6faac..fa9a2a693 100644 --- a/lib/widgets/common/action_controls/togglers/mute.dart +++ b/lib/widgets/common/action_controls/togglers/mute.dart @@ -4,7 +4,7 @@ import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/basic/popup/menu_row.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/buttons/captioned_button.dart'; -import 'package:aves/widgets/viewer/video/controller.dart'; +import 'package:aves_video/aves_video.dart'; import 'package:flutter/material.dart'; class MuteToggler extends StatelessWidget { diff --git a/lib/widgets/common/action_controls/togglers/play.dart b/lib/widgets/common/action_controls/togglers/play.dart index e2f5bbe6b..7ca45ffe3 100644 --- a/lib/widgets/common/action_controls/togglers/play.dart +++ b/lib/widgets/common/action_controls/togglers/play.dart @@ -5,7 +5,7 @@ import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/basic/popup/menu_row.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/buttons/captioned_button.dart'; -import 'package:aves/widgets/viewer/video/controller.dart'; +import 'package:aves_video/aves_video.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; diff --git a/lib/widgets/common/action_mixins/entry_editor.dart b/lib/widgets/common/action_mixins/entry_editor.dart index 40471bbde..ff1ae2d70 100644 --- a/lib/widgets/common/action_mixins/entry_editor.dart +++ b/lib/widgets/common/action_mixins/entry_editor.dart @@ -1,5 +1,6 @@ -import 'package:aves/model/entry.dart'; -import 'package:aves/model/entry_metadata_edition.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/metadata_edition.dart'; +import 'package:aves/model/entry/extensions/multipage.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/placeholder.dart'; import 'package:aves/model/filters/tag.dart'; diff --git a/lib/widgets/common/action_mixins/entry_storage.dart b/lib/widgets/common/action_mixins/entry_storage.dart index e3c1e5a12..768a2d085 100644 --- a/lib/widgets/common/action_mixins/entry_storage.dart +++ b/lib/widgets/common/action_mixins/entry_storage.dart @@ -3,7 +3,9 @@ import 'dart:io'; import 'package:aves/app_mode.dart'; import 'package:aves/model/actions/move_type.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/multipage.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/trash.dart'; import 'package:aves/model/highlight.dart'; diff --git a/lib/widgets/common/action_mixins/permission_aware.dart b/lib/widgets/common/action_mixins/permission_aware.dart index db158802b..5d8fa13f9 100644 --- a/lib/widgets/common/action_mixins/permission_aware.dart +++ b/lib/widgets/common/action_mixins/permission_aware.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; diff --git a/lib/widgets/common/action_mixins/size_aware.dart b/lib/widgets/common/action_mixins/size_aware.dart index cfcb39df0..d1a93321b 100644 --- a/lib/widgets/common/action_mixins/size_aware.dart +++ b/lib/widgets/common/action_mixins/size_aware.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'dart:math'; import 'package:aves/model/actions/move_type.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/utils/collection_utils.dart'; diff --git a/lib/widgets/common/grid/theme.dart b/lib/widgets/common/grid/theme.dart index 2e7bdd1ef..72aabace6 100644 --- a/lib/widgets/common/grid/theme.dart +++ b/lib/widgets/common/grid/theme.dart @@ -1,6 +1,9 @@ import 'dart:math'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/favourites.dart'; +import 'package:aves/model/entry/extensions/multipage.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/widgets/common/identity/aves_icons.dart'; diff --git a/lib/widgets/common/identity/aves_icons.dart b/lib/widgets/common/identity/aves_icons.dart index 20a3443d9..0428f0ca0 100644 --- a/lib/widgets/common/identity/aves_icons.dart +++ b/lib/widgets/common/identity/aves_icons.dart @@ -1,6 +1,8 @@ import 'package:aves/image_providers/app_icon_image_provider.dart'; import 'package:aves/model/covers.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/multipage.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/vaults/vaults.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/utils/android_file_utils.dart'; diff --git a/lib/widgets/common/map/geo_map.dart b/lib/widgets/common/map/geo_map.dart index 0643b2ce4..e18bda838 100644 --- a/lib/widgets/common/map/geo_map.dart +++ b/lib/widgets/common/map/geo_map.dart @@ -2,8 +2,10 @@ import 'dart:async'; import 'dart:math'; import 'dart:ui'; -import 'package:aves/model/entry.dart'; -import 'package:aves/model/entry_images.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/location.dart'; +import 'package:aves/model/entry/extensions/images.dart'; +import 'package:aves/model/entry/sort.dart'; import 'package:aves/model/settings/enums/map_style.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/services/common/services.dart'; @@ -343,7 +345,7 @@ class _GeoMapState extends State { ZoomedBounds? _initBoundsForEntries({required List entries, int? recentCount}) { if (recentCount != null) { - entries = List.of(entries)..sort(AvesEntry.compareByDate); + entries = List.of(entries)..sort(AvesEntrySort.compareByDate); entries = entries.take(recentCount).toList(); } diff --git a/lib/widgets/common/thumbnail/decorated.dart b/lib/widgets/common/thumbnail/decorated.dart index 93ccf66ac..9240b1f12 100644 --- a/lib/widgets/common/thumbnail/decorated.dart +++ b/lib/widgets/common/thumbnail/decorated.dart @@ -1,4 +1,5 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/widgets/common/fx/borders.dart'; import 'package:aves/widgets/common/grid/overlay.dart'; import 'package:aves/widgets/common/thumbnail/image.dart'; diff --git a/lib/widgets/common/thumbnail/error.dart b/lib/widgets/common/thumbnail/error.dart index bb6fa6000..0ad1eb85f 100644 --- a/lib/widgets/common/thumbnail/error.dart +++ b/lib/widgets/common/thumbnail/error.dart @@ -1,7 +1,7 @@ import 'dart:io'; import 'dart:math'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/utils/mime_utils.dart'; import 'package:flutter/foundation.dart'; diff --git a/lib/widgets/common/thumbnail/image.dart b/lib/widgets/common/thumbnail/image.dart index 9d7dd5c55..a639f475f 100644 --- a/lib/widgets/common/thumbnail/image.dart +++ b/lib/widgets/common/thumbnail/image.dart @@ -2,8 +2,9 @@ import 'dart:math'; import 'dart:ui'; import 'package:aves/image_providers/thumbnail_provider.dart'; -import 'package:aves/model/entry.dart'; -import 'package:aves/model/entry_images.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/images.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/enums/entry_background.dart'; import 'package:aves/model/settings/enums/enums.dart'; diff --git a/lib/widgets/common/thumbnail/notifications.dart b/lib/widgets/common/thumbnail/notifications.dart index b52226070..d5edca496 100644 --- a/lib/widgets/common/thumbnail/notifications.dart +++ b/lib/widgets/common/thumbnail/notifications.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:flutter/widgets.dart'; @immutable diff --git a/lib/widgets/common/thumbnail/overlay.dart b/lib/widgets/common/thumbnail/overlay.dart index ed64484d5..6326be3d4 100644 --- a/lib/widgets/common/thumbnail/overlay.dart +++ b/lib/widgets/common/thumbnail/overlay.dart @@ -1,6 +1,6 @@ import 'dart:math'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/highlight.dart'; import 'package:aves/model/selection.dart'; import 'package:aves/theme/durations.dart'; diff --git a/lib/widgets/common/thumbnail/scroller.dart b/lib/widgets/common/thumbnail/scroller.dart index 25cf3d955..ab5680dd6 100644 --- a/lib/widgets/common/thumbnail/scroller.dart +++ b/lib/widgets/common/thumbnail/scroller.dart @@ -1,6 +1,6 @@ import 'dart:math'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/widgets/common/behaviour/known_extent_scroll_physics.dart'; import 'package:aves/widgets/common/grid/theme.dart'; diff --git a/lib/widgets/debug/database.dart b/lib/widgets/debug/database.dart index dca8795fa..d5d2eb5cf 100644 --- a/lib/widgets/debug/database.dart +++ b/lib/widgets/debug/database.dart @@ -1,5 +1,5 @@ import 'package:aves/model/covers.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/favourites.dart'; import 'package:aves/model/metadata/address.dart'; import 'package:aves/model/metadata/catalog.dart'; diff --git a/lib/widgets/dialogs/add_shortcut_dialog.dart b/lib/widgets/dialogs/add_shortcut_dialog.dart index f25fc4108..f445e3df6 100644 --- a/lib/widgets/dialogs/add_shortcut_dialog.dart +++ b/lib/widgets/dialogs/add_shortcut_dialog.dart @@ -1,5 +1,5 @@ import 'package:aves/model/covers.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/filters/query.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; diff --git a/lib/widgets/dialogs/convert_entry_dialog.dart b/lib/widgets/dialogs/convert_entry_dialog.dart index 6fe70a92e..090c1b0f4 100644 --- a/lib/widgets/dialogs/convert_entry_dialog.dart +++ b/lib/widgets/dialogs/convert_entry_dialog.dart @@ -1,4 +1,5 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/metadata/enums/enums.dart'; import 'package:aves/model/metadata/enums/length_unit.dart'; import 'package:aves/model/settings/settings.dart'; @@ -148,7 +149,7 @@ class _ConvertEntryDialogState extends State { ), ), const SizedBox(width: 8), - const Text(AvesEntry.resolutionSeparator), + const Text(ExtraAvesEntryProps.resolutionSeparator), const SizedBox(width: 8), Expanded( child: TextField( diff --git a/lib/widgets/dialogs/entry_editors/edit_date_dialog.dart b/lib/widgets/dialogs/entry_editors/edit_date_dialog.dart index ea1f51447..de368dc55 100644 --- a/lib/widgets/dialogs/entry_editors/edit_date_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/edit_date_dialog.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/metadata/date_modifier.dart'; import 'package:aves/model/metadata/enums/date_edit_action.dart'; import 'package:aves/model/metadata/enums/date_field_source.dart'; diff --git a/lib/widgets/dialogs/entry_editors/edit_description_dialog.dart b/lib/widgets/dialogs/entry_editors/edit_description_dialog.dart index 4478de495..e60e49cae 100644 --- a/lib/widgets/dialogs/entry_editors/edit_description_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/edit_description_dialog.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry_metadata_edition.dart'; +import 'package:aves/model/entry/extensions/metadata_edition.dart'; import 'package:aves/widgets/common/basic/labeled_checkbox.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; diff --git a/lib/widgets/dialogs/entry_editors/edit_location_dialog.dart b/lib/widgets/dialogs/entry_editors/edit_location_dialog.dart index 1b81adf64..f5e6a8e34 100644 --- a/lib/widgets/dialogs/entry_editors/edit_location_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/edit_location_dialog.dart @@ -1,5 +1,6 @@ -import 'package:aves/model/entry.dart'; -import 'package:aves/model/entry_metadata_edition.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/location.dart'; +import 'package:aves/model/entry/extensions/metadata_edition.dart'; import 'package:aves/model/metadata/enums/enums.dart'; import 'package:aves/model/metadata/enums/location_edit_action.dart'; import 'package:aves/model/settings/enums/coordinate_format.dart'; diff --git a/lib/widgets/dialogs/entry_editors/edit_rating_dialog.dart b/lib/widgets/dialogs/entry_editors/edit_rating_dialog.dart index e1dab151a..c118cbb8f 100644 --- a/lib/widgets/dialogs/entry_editors/edit_rating_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/edit_rating_dialog.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; diff --git a/lib/widgets/dialogs/entry_editors/rename_entry_dialog.dart b/lib/widgets/dialogs/entry_editors/rename_entry_dialog.dart index 262b60474..ff1a175a8 100644 --- a/lib/widgets/dialogs/entry_editors/rename_entry_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/rename_entry_dialog.dart @@ -1,6 +1,6 @@ import 'dart:io'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/utils/constants.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; diff --git a/lib/widgets/dialogs/entry_editors/rename_entry_set_page.dart b/lib/widgets/dialogs/entry_editors/rename_entry_set_page.dart index 5980f0993..55d3f68d7 100644 --- a/lib/widgets/dialogs/entry_editors/rename_entry_set_page.dart +++ b/lib/widgets/dialogs/entry_editors/rename_entry_set_page.dart @@ -1,6 +1,6 @@ import 'dart:math'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/naming_pattern.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/durations.dart'; diff --git a/lib/widgets/dialogs/entry_editors/tag_editor_page.dart b/lib/widgets/dialogs/entry_editors/tag_editor_page.dart index fe4a06c52..87d5e66a1 100644 --- a/lib/widgets/dialogs/entry_editors/tag_editor_page.dart +++ b/lib/widgets/dialogs/entry_editors/tag_editor_page.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/placeholder.dart'; import 'package:aves/model/filters/tag.dart'; diff --git a/lib/widgets/dialogs/filter_editors/cover_selection_dialog.dart b/lib/widgets/dialogs/filter_editors/cover_selection_dialog.dart index afbeb28f2..30aee7b04 100644 --- a/lib/widgets/dialogs/filter_editors/cover_selection_dialog.dart +++ b/lib/widgets/dialogs/filter_editors/cover_selection_dialog.dart @@ -1,7 +1,7 @@ import 'dart:math'; import 'package:aves/image_providers/app_icon_image_provider.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/enums/enums.dart'; diff --git a/lib/widgets/dialogs/item_picker.dart b/lib/widgets/dialogs/item_picker.dart index 2c5c2631d..1b908c1f0 100644 --- a/lib/widgets/dialogs/item_picker.dart +++ b/lib/widgets/dialogs/item_picker.dart @@ -1,6 +1,6 @@ import 'dart:math'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/fx/borders.dart'; diff --git a/lib/widgets/dialogs/pick_dialogs/item_pick_page.dart b/lib/widgets/dialogs/pick_dialogs/item_pick_page.dart index 72ad90cc0..4631a2b94 100644 --- a/lib/widgets/dialogs/pick_dialogs/item_pick_page.dart +++ b/lib/widgets/dialogs/pick_dialogs/item_pick_page.dart @@ -1,5 +1,5 @@ import 'package:aves/app_mode.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/filters/query.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/widgets/collection/collection_grid.dart'; diff --git a/lib/widgets/dialogs/video_stream_selection_dialog.dart b/lib/widgets/dialogs/video_stream_selection_dialog.dart index eb0e65350..59787370f 100644 --- a/lib/widgets/dialogs/video_stream_selection_dialog.dart +++ b/lib/widgets/dialogs/video_stream_selection_dialog.dart @@ -1,10 +1,11 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/ref/languages.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/theme/themes.dart'; import 'package:aves/widgets/common/basic/text_dropdown_button.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:aves/widgets/viewer/video/controller.dart'; +import 'package:aves_video/aves_video.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; @@ -117,7 +118,7 @@ class _VideoStreamSelectionDialogState extends State final w = stream.width; final h = stream.height; if (w != null && h != null) { - return '$common • $w${AvesEntry.resolutionSeparator}$h'; + return '$common • $w${ExtraAvesEntryProps.resolutionSeparator}$h'; } } return common; diff --git a/lib/widgets/filter_grids/albums_page.dart b/lib/widgets/filter_grids/albums_page.dart index b39500edd..5cd758ffb 100644 --- a/lib/widgets/filter_grids/albums_page.dart +++ b/lib/widgets/filter_grids/albums_page.dart @@ -1,4 +1,5 @@ import 'package:aves/model/covers.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/settings.dart'; diff --git a/lib/widgets/filter_grids/common/action_delegates/album_set.dart b/lib/widgets/filter_grids/common/action_delegates/album_set.dart index 1565080f7..207802309 100644 --- a/lib/widgets/filter_grids/common/action_delegates/album_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/album_set.dart @@ -4,7 +4,7 @@ import 'package:aves/app_mode.dart'; import 'package:aves/model/actions/chip_set_actions.dart'; import 'package:aves/model/actions/move_type.dart'; import 'package:aves/model/device.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/highlight.dart'; diff --git a/lib/widgets/filter_grids/common/action_delegates/chip_set.dart b/lib/widgets/filter_grids/common/action_delegates/chip_set.dart index 50e7dde04..a9b9c20a2 100644 --- a/lib/widgets/filter_grids/common/action_delegates/chip_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/chip_set.dart @@ -1,7 +1,7 @@ import 'package:aves/app_mode.dart'; import 'package:aves/model/actions/chip_set_actions.dart'; import 'package:aves/model/covers.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/query.dart'; diff --git a/lib/widgets/filter_grids/common/filter_grid_page.dart b/lib/widgets/filter_grids/common/filter_grid_page.dart index 38d773180..35c249d8a 100644 --- a/lib/widgets/filter_grids/common/filter_grid_page.dart +++ b/lib/widgets/filter_grids/common/filter_grid_page.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:aves/app_mode.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/highlight.dart'; import 'package:aves/model/query.dart'; @@ -12,8 +13,8 @@ import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/model/vaults/vaults.dart'; import 'package:aves/theme/colors.dart'; import 'package:aves/theme/durations.dart'; -import 'package:aves/widgets/common/basic/draggable_scrollbar/scrollbar.dart'; import 'package:aves/widgets/common/basic/draggable_scrollbar/notifications.dart'; +import 'package:aves/widgets/common/basic/draggable_scrollbar/scrollbar.dart'; import 'package:aves/widgets/common/basic/insets.dart'; import 'package:aves/widgets/common/basic/scaffold.dart'; import 'package:aves/widgets/common/behaviour/pop/double_back.dart'; diff --git a/lib/widgets/filter_grids/common/list_details.dart b/lib/widgets/filter_grids/common/list_details.dart index 84c11a2e3..d7cacb7d7 100644 --- a/lib/widgets/filter_grids/common/list_details.dart +++ b/lib/widgets/filter_grids/common/list_details.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/source/collection_source.dart'; diff --git a/lib/widgets/home_page.dart b/lib/widgets/home_page.dart index e03454aaf..feb55bf88 100644 --- a/lib/widgets/home_page.dart +++ b/lib/widgets/home_page.dart @@ -1,7 +1,8 @@ import 'dart:async'; import 'package:aves/app_mode.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/catalog.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/enums/enums.dart'; diff --git a/lib/widgets/home_widget.dart b/lib/widgets/home_widget.dart index 95256f867..69f8c61b1 100644 --- a/lib/widgets/home_widget.dart +++ b/lib/widgets/home_widget.dart @@ -2,8 +2,8 @@ import 'dart:async'; import 'dart:typed_data'; import 'dart:ui' as ui; -import 'package:aves/model/entry.dart'; -import 'package:aves/model/entry_images.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/images.dart'; import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/enums/widget_shape.dart'; import 'package:aves/utils/constants.dart'; diff --git a/lib/widgets/map/map_info_row.dart b/lib/widgets/map/map_info_row.dart index 1bef21f85..67a600cbf 100644 --- a/lib/widgets/map/map_info_row.dart +++ b/lib/widgets/map/map_info_row.dart @@ -1,4 +1,5 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/location.dart'; import 'package:aves/model/settings/enums/coordinate_format.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/services/common/services.dart'; diff --git a/lib/widgets/map/map_page.dart b/lib/widgets/map/map_page.dart index b3629257b..5cf00c41b 100644 --- a/lib/widgets/map/map_page.dart +++ b/lib/widgets/map/map_page.dart @@ -3,7 +3,8 @@ import 'dart:async'; import 'package:aves/app_mode.dart'; import 'package:aves/model/actions/map_actions.dart'; import 'package:aves/model/actions/map_cluster_actions.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/location.dart'; import 'package:aves/model/filters/coordinate.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/geotiff.dart'; @@ -31,8 +32,8 @@ import 'package:aves/widgets/common/providers/map_theme_provider.dart'; import 'package:aves/widgets/common/thumbnail/scroller.dart'; import 'package:aves/widgets/filter_grids/common/action_delegates/chip.dart'; import 'package:aves/widgets/map/map_info_row.dart'; -import 'package:aves/widgets/viewer/entry_viewer_page.dart'; import 'package:aves/widgets/viewer/controls/notifications.dart'; +import 'package:aves/widgets/viewer/entry_viewer_page.dart'; import 'package:aves_map/aves_map.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/stats/date/histogram.dart b/lib/widgets/stats/date/histogram.dart index bf268a171..772c07310 100644 --- a/lib/widgets/stats/date/histogram.dart +++ b/lib/widgets/stats/date/histogram.dart @@ -1,7 +1,8 @@ import 'dart:async'; import 'dart:math'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/sort.dart'; import 'package:aves/model/filters/date.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; @@ -50,7 +51,7 @@ class _HistogramState extends State with AutomaticKeepAliveClientMixi void initState() { super.initState(); - final entriesByDateDescending = List.of(widget.entries)..sort(AvesEntry.compareByDate); + final entriesByDateDescending = List.of(widget.entries)..sort(AvesEntrySort.compareByDate); var lastDate = entriesByDateDescending.firstWhereOrNull((entry) => entry.bestDate != null)?.bestDate; var firstDate = entriesByDateDescending.lastWhereOrNull((entry) => entry.bestDate != null)?.bestDate; diff --git a/lib/widgets/stats/stats_page.dart b/lib/widgets/stats/stats_page.dart index ac28daf8d..1a6ff3ff6 100644 --- a/lib/widgets/stats/stats_page.dart +++ b/lib/widgets/stats/stats_page.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/location.dart'; diff --git a/lib/widgets/viewer/action/entry_action_delegate.dart b/lib/widgets/viewer/action/entry_action_delegate.dart index 094035435..ee5ed6e72 100644 --- a/lib/widgets/viewer/action/entry_action_delegate.dart +++ b/lib/widgets/viewer/action/entry_action_delegate.dart @@ -6,8 +6,12 @@ import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/model/actions/move_type.dart'; import 'package:aves/model/actions/share_actions.dart'; import 'package:aves/model/device.dart'; -import 'package:aves/model/entry.dart'; -import 'package:aves/model/entry_metadata_edition.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/favourites.dart'; +import 'package:aves/model/entry/extensions/location.dart'; +import 'package:aves/model/entry/extensions/metadata_edition.dart'; +import 'package:aves/model/entry/extensions/multipage.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/settings.dart'; @@ -243,7 +247,11 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix case EntryAction.openVideo: final controller = context.read().getController(targetEntry); if (controller != null) { - VideoActionNotification(controller: controller, action: action).dispatch(context); + VideoActionNotification( + controller: controller, + entry: targetEntry, + action: action, + ).dispatch(context); } break; case EntryAction.edit: diff --git a/lib/widgets/viewer/action/entry_info_action_delegate.dart b/lib/widgets/viewer/action/entry_info_action_delegate.dart index 469252264..62eeffbd0 100644 --- a/lib/widgets/viewer/action/entry_info_action_delegate.dart +++ b/lib/widgets/viewer/action/entry_info_action_delegate.dart @@ -4,9 +4,11 @@ import 'dart:convert'; import 'package:aves/app_mode.dart'; import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/model/actions/events.dart'; -import 'package:aves/model/entry.dart'; -import 'package:aves/model/entry_info.dart'; -import 'package:aves/model/entry_metadata_edition.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/info.dart'; +import 'package:aves/model/entry/extensions/metadata_edition.dart'; +import 'package:aves/model/entry/extensions/multipage.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/geotiff.dart'; import 'package:aves/model/settings/settings.dart'; diff --git a/lib/widgets/viewer/action/printer.dart b/lib/widgets/viewer/action/printer.dart index 27f8a80ac..d2969c953 100644 --- a/lib/widgets/viewer/action/printer.dart +++ b/lib/widgets/viewer/action/printer.dart @@ -1,8 +1,10 @@ import 'dart:async'; import 'dart:convert'; -import 'package:aves/model/entry.dart'; -import 'package:aves/model/entry_images.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/images.dart'; +import 'package:aves/model/entry/extensions/multipage.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/widgets/common/action_mixins/feedback.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; diff --git a/lib/widgets/viewer/action/single_entry_editor.dart b/lib/widgets/viewer/action/single_entry_editor.dart index 3bdf65348..bee999ece 100644 --- a/lib/widgets/viewer/action/single_entry_editor.dart +++ b/lib/widgets/viewer/action/single_entry_editor.dart @@ -1,7 +1,9 @@ import 'dart:async'; import 'package:aves/app_mode.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/catalog.dart'; +import 'package:aves/model/entry/extensions/location.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/services/common/services.dart'; diff --git a/lib/widgets/viewer/action/video_action_delegate.dart b/lib/widgets/viewer/action/video_action_delegate.dart index e9562998a..81d91e871 100644 --- a/lib/widgets/viewer/action/video_action_delegate.dart +++ b/lib/widgets/viewer/action/video_action_delegate.dart @@ -1,6 +1,9 @@ import 'dart:async'; import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/location.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/services/common/services.dart'; @@ -18,7 +21,7 @@ import 'package:aves/widgets/dialogs/video_speed_dialog.dart'; import 'package:aves/widgets/dialogs/video_stream_selection_dialog.dart'; import 'package:aves/widgets/settings/video/video_settings_page.dart'; import 'package:aves/widgets/viewer/controls/notifications.dart'; -import 'package:aves/widgets/viewer/video/controller.dart'; +import 'package:aves_video/aves_video.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -35,14 +38,14 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix stopOverlayHidingTimer(); } - Future onActionSelected(BuildContext context, AvesVideoController controller, EntryAction action) async { + Future onActionSelected(BuildContext context, AvesEntry entry, AvesVideoController controller, EntryAction action) async { // make sure overlay is not disappearing when selecting an action stopOverlayHidingTimer(); const ToggleOverlayNotification(visible: true).dispatch(context); switch (action) { case EntryAction.videoCaptureFrame: - await _captureFrame(context, controller); + await _captureFrame(context, entry, controller); break; case EntryAction.videoToggleMute: await controller.mute(!controller.isMuted); @@ -66,7 +69,6 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix await controller.seekTo(controller.currentPosition + 10000); break; case EntryAction.openVideo: - final entry = controller.entry; await androidAppService.open(entry.uri, entry.mimeTypeAnySubtype, forceChooser: false).then((success) { if (!success) showNoMatchingAppDialog(context); }); @@ -76,7 +78,7 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix } } - Future _captureFrame(BuildContext context, AvesVideoController controller) async { + Future _captureFrame(BuildContext context, AvesEntry entry, AvesVideoController controller) async { final positionMillis = controller.currentPosition; final bytes = await controller.captureFrame(); @@ -85,7 +87,6 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix if (!await checkFreeSpace(context, bytes.length, destinationAlbum)) return; - final entry = controller.entry; final rotationDegrees = entry.rotationDegrees; final dateTimeMillis = entry.catalogMetadata?.dateMillis; final latLng = entry.latLng; diff --git a/lib/widgets/viewer/controls/controller.dart b/lib/widgets/viewer/controls/controller.dart index 3ee3f2a6e..5808a11ee 100644 --- a/lib/widgets/viewer/controls/controller.dart +++ b/lib/widgets/viewer/controls/controller.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'dart:math'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/widgets/viewer/controls/events.dart'; diff --git a/lib/widgets/viewer/controls/notifications.dart b/lib/widgets/viewer/controls/notifications.dart index 74850f6dd..5cb76faa7 100644 --- a/lib/widgets/viewer/controls/notifications.dart +++ b/lib/widgets/viewer/controls/notifications.dart @@ -1,8 +1,8 @@ import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/model/actions/move_type.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/filters/filters.dart'; -import 'package:aves/widgets/viewer/video/controller.dart'; +import 'package:aves_video/aves_video.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/widgets.dart'; @@ -62,10 +62,12 @@ class TvShowMoreInfoNotification extends Notification {} @immutable class VideoActionNotification extends Notification { final AvesVideoController controller; + final AvesEntry entry; final EntryAction action; const VideoActionNotification({ required this.controller, + required this.entry, required this.action, }); } diff --git a/lib/widgets/viewer/debug/db.dart b/lib/widgets/viewer/debug/db.dart index a7a039e8e..fded8c92d 100644 --- a/lib/widgets/viewer/debug/db.dart +++ b/lib/widgets/viewer/debug/db.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/metadata/address.dart'; import 'package:aves/model/metadata/catalog.dart'; import 'package:aves/model/metadata/trash.dart'; diff --git a/lib/widgets/viewer/debug/debug_page.dart b/lib/widgets/viewer/debug/debug_page.dart index 28b22af83..a45b213ed 100644 --- a/lib/widgets/viewer/debug/debug_page.dart +++ b/lib/widgets/viewer/debug/debug_page.dart @@ -1,6 +1,10 @@ import 'package:aves/app_mode.dart'; -import 'package:aves/model/entry.dart'; -import 'package:aves/model/entry_images.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/favourites.dart'; +import 'package:aves/model/entry/extensions/location.dart'; +import 'package:aves/model/entry/extensions/images.dart'; +import 'package:aves/model/entry/extensions/multipage.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/viewer/debug/db.dart'; import 'package:aves/widgets/viewer/debug/metadata.dart'; diff --git a/lib/widgets/viewer/debug/metadata.dart b/lib/widgets/viewer/debug/metadata.dart index 0d6e47327..99e403ccc 100644 --- a/lib/widgets/viewer/debug/metadata.dart +++ b/lib/widgets/viewer/debug/metadata.dart @@ -1,7 +1,7 @@ import 'dart:collection'; import 'dart:typed_data'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/ref/mime_types.dart'; import 'package:aves/services/android_debug_service.dart'; import 'package:aves/utils/constants.dart'; diff --git a/lib/widgets/viewer/entry_horizontal_pager.dart b/lib/widgets/viewer/entry_horizontal_pager.dart index 82bad368e..c491edad4 100644 --- a/lib/widgets/viewer/entry_horizontal_pager.dart +++ b/lib/widgets/viewer/entry_horizontal_pager.dart @@ -1,4 +1,6 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/multipage.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/enums/viewer_transition.dart'; import 'package:aves/model/settings/settings.dart'; diff --git a/lib/widgets/viewer/entry_vertical_pager.dart b/lib/widgets/viewer/entry_vertical_pager.dart index 3f7c7a0d2..37fd1c6f5 100644 --- a/lib/widgets/viewer/entry_vertical_pager.dart +++ b/lib/widgets/viewer/entry_vertical_pager.dart @@ -4,7 +4,10 @@ import 'dart:ui'; import 'package:aves/app_mode.dart'; import 'package:aves/model/actions/entry_actions.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/catalog.dart'; +import 'package:aves/model/entry/extensions/location.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/theme/durations.dart'; @@ -240,7 +243,7 @@ class _ViewerVerticalPageViewState extends State { ShowInfoIntent: CallbackAction(onInvoke: (intent) => ShowInfoPageNotification().dispatch(context)), TvShowLessInfoIntent: CallbackAction(onInvoke: (intent) => TvShowLessInfoNotification().dispatch(context)), TvShowMoreInfoIntent: CallbackAction(onInvoke: (intent) => TvShowMoreInfoNotification().dispatch(context)), - PlayPauseIntent: CallbackAction(onInvoke: (intent) => _onPlayPauseIntent(intent, entry)), + PlayPauseIntent: CallbackAction(onInvoke: (intent) => _onPlayPauseIntent(intent)), EntryActionIntent: CallbackAction(onInvoke: (intent) => _onEntryActionIntent(intent.action)), ActivateIntent: CallbackAction(onInvoke: (intent) { if (useTvLayout) { @@ -249,7 +252,11 @@ class _ViewerVerticalPageViewState extends State { // address `TV-PC` requirement from https://developer.android.com/docs/quality-guidelines/tv-app-quality final controller = context.read().getController(_entry); if (controller != null) { - VideoActionNotification(controller: controller, action: EntryAction.videoTogglePlay).dispatch(context); + VideoActionNotification( + controller: controller, + entry: _entry, + action: EntryAction.videoTogglePlay, + ).dispatch(context); } } else { const ToggleOverlayNotification().dispatch(context); @@ -357,7 +364,7 @@ class _ViewerVerticalPageViewState extends State { } } - void _onPlayPauseIntent(PlayPauseIntent intent, entry) { + void _onPlayPauseIntent(PlayPauseIntent intent) { // address `TV-PP` requirement from https://developer.android.com/docs/quality-guidelines/tv-app-quality final _entry = entry; if (_entry != null && _entry.isVideo) { @@ -376,7 +383,11 @@ class _ViewerVerticalPageViewState extends State { break; } if (toggle) { - VideoActionNotification(controller: controller, action: EntryAction.videoTogglePlay).dispatch(context); + VideoActionNotification( + controller: controller, + entry: _entry, + action: EntryAction.videoTogglePlay, + ).dispatch(context); } } } diff --git a/lib/widgets/viewer/entry_viewer_page.dart b/lib/widgets/viewer/entry_viewer_page.dart index 527da25df..94ef5b5d7 100644 --- a/lib/widgets/viewer/entry_viewer_page.dart +++ b/lib/widgets/viewer/entry_viewer_page.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/widgets/common/basic/scaffold.dart'; import 'package:aves/widgets/viewer/controls/controller.dart'; diff --git a/lib/widgets/viewer/entry_viewer_stack.dart b/lib/widgets/viewer/entry_viewer_stack.dart index 45dda3132..876ef773b 100644 --- a/lib/widgets/viewer/entry_viewer_stack.dart +++ b/lib/widgets/viewer/entry_viewer_stack.dart @@ -4,7 +4,9 @@ import 'package:aves/app_mode.dart'; import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/model/actions/move_type.dart'; import 'package:aves/model/device.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/multipage.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/trash.dart'; import 'package:aves/model/highlight.dart'; @@ -31,7 +33,7 @@ import 'package:aves/widgets/viewer/overlay/top.dart'; import 'package:aves/widgets/viewer/overlay/video/video.dart'; import 'package:aves/widgets/viewer/page_entry_builder.dart'; import 'package:aves/widgets/viewer/video/conductor.dart'; -import 'package:aves/widgets/viewer/video/controller.dart'; +import 'package:aves_video/aves_video.dart'; import 'package:aves/widgets/viewer/visual/conductor.dart'; import 'package:aves/widgets/viewer/visual/controller_mixin.dart'; import 'package:collection/collection.dart'; @@ -373,7 +375,12 @@ class _EntryViewerStackState extends State with EntryViewContr scale: _overlayVideoControlScale, onActionSelected: (action) { if (videoController != null) { - _onVideoAction(context, videoController, action); + _onVideoAction( + context: context, + entry: targetEntry, + controller: videoController, + action: action, + ); } }, onActionMenuOpened: () { @@ -484,9 +491,12 @@ class _EntryViewerStackState extends State with EntryViewContr } else if (notification is ToggleOverlayNotification) { _overlayVisible.value = notification.visible ?? !_overlayVisible.value; } else if (notification is VideoActionNotification) { - final controller = notification.controller; - final action = notification.action; - _onVideoAction(context, controller, action); + _onVideoAction( + context: context, + entry: notification.entry, + controller: notification.controller, + action: notification.action, + ); } else if (notification is TvShowLessInfoNotification) { if (_overlayVisible.value) { _overlayVisible.value = false; @@ -513,8 +523,13 @@ class _EntryViewerStackState extends State with EntryViewContr return true; } - Future _onVideoAction(BuildContext context, AvesVideoController controller, EntryAction action) async { - await _videoActionDelegate.onActionSelected(context, controller, action); + Future _onVideoAction({ + required BuildContext context, + required AvesEntry entry, + required AvesVideoController controller, + required EntryAction action, + }) async { + await _videoActionDelegate.onActionSelected(context, entry, controller, action); if (action == EntryAction.videoToggleMute) { final override = controller.isMuted; videoMutedOverride = override; @@ -747,8 +762,7 @@ class _EntryViewerStackState extends State with EntryViewContr Future _enablePictureInPicture() async { final videoController = context.read().getPlayingController(); if (videoController != null) { - final targetEntry = videoController.entry; - final entrySize = targetEntry.displaySize; + final entrySize = videoController.entry.displaySize; final aspectRatio = Rational(entrySize.width.round(), entrySize.height.round()); final mq = context.read(); diff --git a/lib/widgets/viewer/hero.dart b/lib/widgets/viewer/hero.dart index 6c828d519..3e9c19f9f 100644 --- a/lib/widgets/viewer/hero.dart +++ b/lib/widgets/viewer/hero.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/widgets.dart'; diff --git a/lib/widgets/viewer/info/basic_section.dart b/lib/widgets/viewer/info/basic_section.dart index 4f081f3d2..b1da03c60 100644 --- a/lib/widgets/viewer/info/basic_section.dart +++ b/lib/widgets/viewer/info/basic_section.dart @@ -1,7 +1,10 @@ import 'package:aves/app_mode.dart'; import 'package:aves/image_providers/app_icon_image_provider.dart'; import 'package:aves/model/actions/entry_actions.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/favourites.dart'; +import 'package:aves/model/entry/extensions/multipage.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/favourites.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/favourite.dart'; diff --git a/lib/widgets/viewer/info/embedded/embedded_data_opener.dart b/lib/widgets/viewer/info/embedded/embedded_data_opener.dart index d9f33d63d..ff96ebdcd 100644 --- a/lib/widgets/viewer/info/embedded/embedded_data_opener.dart +++ b/lib/widgets/viewer/info/embedded/embedded_data_opener.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/ref/mime_types.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/widgets/common/action_mixins/feedback.dart'; diff --git a/lib/widgets/viewer/info/info_app_bar.dart b/lib/widgets/viewer/info/info_app_bar.dart index 4e05c0c86..689152407 100644 --- a/lib/widgets/viewer/info/info_app_bar.dart +++ b/lib/widgets/viewer/info/info_app_bar.dart @@ -1,6 +1,7 @@ import 'package:aves/app_mode.dart'; import 'package:aves/model/actions/entry_actions.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/selection.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; diff --git a/lib/widgets/viewer/info/info_page.dart b/lib/widgets/viewer/info/info_page.dart index 26c4dfeb7..3820af71c 100644 --- a/lib/widgets/viewer/info/info_page.dart +++ b/lib/widgets/viewer/info/info_page.dart @@ -2,7 +2,8 @@ import 'dart:async'; import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/model/actions/events.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/multipage.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/selection.dart'; import 'package:aves/model/source/collection_lens.dart'; diff --git a/lib/widgets/viewer/info/info_search.dart b/lib/widgets/viewer/info/info_search.dart index 008fa535e..5f5d48307 100644 --- a/lib/widgets/viewer/info/info_search.dart +++ b/lib/widgets/viewer/info/info_search.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/empty.dart'; diff --git a/lib/widgets/viewer/info/location_section.dart b/lib/widgets/viewer/info/location_section.dart index 208d270fe..158e44a6e 100644 --- a/lib/widgets/viewer/info/location_section.dart +++ b/lib/widgets/viewer/info/location_section.dart @@ -1,5 +1,6 @@ import 'package:aves/app_mode.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/location.dart'; import 'package:aves/model/filters/location.dart'; import 'package:aves/model/settings/enums/coordinate_format.dart'; import 'package:aves/model/settings/settings.dart'; diff --git a/lib/widgets/viewer/info/metadata/metadata_dir_tile.dart b/lib/widgets/viewer/info/metadata/metadata_dir_tile.dart index dc7a8f6f3..c353eb785 100644 --- a/lib/widgets/viewer/info/metadata/metadata_dir_tile.dart +++ b/lib/widgets/viewer/info/metadata/metadata_dir_tile.dart @@ -1,6 +1,6 @@ import 'dart:collection'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/ref/brand_colors.dart'; import 'package:aves/services/metadata/svg_metadata_service.dart'; import 'package:aves/theme/colors.dart'; diff --git a/lib/widgets/viewer/info/metadata/metadata_section.dart b/lib/widgets/viewer/info/metadata/metadata_section.dart index 80e76a7ce..8c4777cb2 100644 --- a/lib/widgets/viewer/info/metadata/metadata_section.dart +++ b/lib/widgets/viewer/info/metadata/metadata_section.dart @@ -1,7 +1,7 @@ import 'dart:async'; -import 'package:aves/model/entry.dart'; -import 'package:aves/model/entry_info.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/info.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; diff --git a/lib/widgets/viewer/info/metadata/metadata_thumbnail.dart b/lib/widgets/viewer/info/metadata/metadata_thumbnail.dart index 542b001eb..77d0407fc 100644 --- a/lib/widgets/viewer/info/metadata/metadata_thumbnail.dart +++ b/lib/widgets/viewer/info/metadata/metadata_thumbnail.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'dart:typed_data'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/services/common/services.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; diff --git a/lib/widgets/viewer/info/metadata/tv_page.dart b/lib/widgets/viewer/info/metadata/tv_page.dart index dbf3033f4..46af8c63b 100644 --- a/lib/widgets/viewer/info/metadata/tv_page.dart +++ b/lib/widgets/viewer/info/metadata/tv_page.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/basic/scaffold.dart'; import 'package:aves/widgets/common/behaviour/intents.dart'; diff --git a/lib/widgets/viewer/multipage/conductor.dart b/lib/widgets/viewer/multipage/conductor.dart index da6915149..bac45d9f2 100644 --- a/lib/widgets/viewer/multipage/conductor.dart +++ b/lib/widgets/viewer/multipage/conductor.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/widgets/viewer/multipage/controller.dart'; import 'package:collection/collection.dart'; diff --git a/lib/widgets/viewer/multipage/controller.dart b/lib/widgets/viewer/multipage/controller.dart index 5a7c68cd6..a2332915f 100644 --- a/lib/widgets/viewer/multipage/controller.dart +++ b/lib/widgets/viewer/multipage/controller.dart @@ -1,6 +1,7 @@ import 'dart:async'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/multipage.dart'; import 'package:aves/model/multipage.dart'; import 'package:flutter/foundation.dart'; diff --git a/lib/widgets/viewer/overlay/bottom.dart b/lib/widgets/viewer/overlay/bottom.dart index 2c3f15efb..d80348978 100644 --- a/lib/widgets/viewer/overlay/bottom.dart +++ b/lib/widgets/viewer/overlay/bottom.dart @@ -1,7 +1,8 @@ import 'dart:math'; import 'package:aves/app_mode.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/multipage.dart'; import 'package:aves/model/selection.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; diff --git a/lib/widgets/viewer/overlay/details/date.dart b/lib/widgets/viewer/overlay/details/date.dart index da3bdb092..67e24fadd 100644 --- a/lib/widgets/viewer/overlay/details/date.dart +++ b/lib/widgets/viewer/overlay/details/date.dart @@ -1,4 +1,5 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/theme/format.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/utils/constants.dart'; diff --git a/lib/widgets/viewer/overlay/details/details.dart b/lib/widgets/viewer/overlay/details/details.dart index d24265ee7..3b10442c1 100644 --- a/lib/widgets/viewer/overlay/details/details.dart +++ b/lib/widgets/viewer/overlay/details/details.dart @@ -1,6 +1,6 @@ import 'dart:math'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/metadata/overlay.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/services/common/services.dart'; diff --git a/lib/widgets/viewer/overlay/details/location.dart b/lib/widgets/viewer/overlay/details/location.dart index aecf9dd2a..ad3ebd4af 100644 --- a/lib/widgets/viewer/overlay/details/location.dart +++ b/lib/widgets/viewer/overlay/details/location.dart @@ -1,4 +1,5 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/location.dart'; import 'package:aves/model/settings/enums/coordinate_format.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/icons.dart'; diff --git a/lib/widgets/viewer/overlay/details/position_title.dart b/lib/widgets/viewer/overlay/details/position_title.dart index 9d4767925..d13640e95 100644 --- a/lib/widgets/viewer/overlay/details/position_title.dart +++ b/lib/widgets/viewer/overlay/details/position_title.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/multipage.dart'; import 'package:aves/utils/constants.dart'; import 'package:aves/widgets/viewer/multipage/controller.dart'; diff --git a/lib/widgets/viewer/overlay/details/rating_tags.dart b/lib/widgets/viewer/overlay/details/rating_tags.dart index f99c222a1..9a837f478 100644 --- a/lib/widgets/viewer/overlay/details/rating_tags.dart +++ b/lib/widgets/viewer/overlay/details/rating_tags.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/utils/constants.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; diff --git a/lib/widgets/viewer/overlay/panorama.dart b/lib/widgets/viewer/overlay/panorama.dart index 5f853f583..2f58aae82 100644 --- a/lib/widgets/viewer/overlay/panorama.dart +++ b/lib/widgets/viewer/overlay/panorama.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/buttons/overlay_button.dart'; diff --git a/lib/widgets/viewer/overlay/selection_button.dart b/lib/widgets/viewer/overlay/selection_button.dart index ad2f1b899..874492854 100644 --- a/lib/widgets/viewer/overlay/selection_button.dart +++ b/lib/widgets/viewer/overlay/selection_button.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/selection.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; diff --git a/lib/widgets/viewer/overlay/thumbnail_preview.dart b/lib/widgets/viewer/overlay/thumbnail_preview.dart index a7fa1119a..934ad7518 100644 --- a/lib/widgets/viewer/overlay/thumbnail_preview.dart +++ b/lib/widgets/viewer/overlay/thumbnail_preview.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/utils/debouncer.dart'; import 'package:aves/widgets/common/thumbnail/scroller.dart'; diff --git a/lib/widgets/viewer/overlay/top.dart b/lib/widgets/viewer/overlay/top.dart index 7021e0149..67cd07c31 100644 --- a/lib/widgets/viewer/overlay/top.dart +++ b/lib/widgets/viewer/overlay/top.dart @@ -1,4 +1,5 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/multipage.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/themes.dart'; import 'package:aves/widgets/common/fx/blurred.dart'; diff --git a/lib/widgets/viewer/overlay/video/controls.dart b/lib/widgets/viewer/overlay/video/controls.dart index 71db0d998..d2522896f 100644 --- a/lib/widgets/viewer/overlay/video/controls.dart +++ b/lib/widgets/viewer/overlay/video/controls.dart @@ -1,13 +1,15 @@ import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/widgets/common/action_controls/togglers/play.dart'; import 'package:aves/widgets/common/identity/buttons/overlay_button.dart'; -import 'package:aves/widgets/viewer/video/controller.dart'; +import 'package:aves_video/aves_video.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; class VideoControlRow extends StatelessWidget { + final AvesEntry entry; final AvesVideoController? controller; final Animation scale; final Function(EntryAction value) onActionSelected; @@ -17,6 +19,7 @@ class VideoControlRow extends StatelessWidget { const VideoControlRow({ super.key, + required this.entry, required this.controller, required this.scale, required this.onActionSelected, @@ -63,10 +66,9 @@ class VideoControlRow extends StatelessWidget { ], ); case VideoControls.playOutside: - final trashed = controller?.entry.trashed ?? false; return Padding( padding: const EdgeInsetsDirectional.only(start: padding), - child: _buildIconButton(context, EntryAction.openVideo, enabled: !trashed), + child: _buildIconButton(context, EntryAction.openVideo, enabled: !entry.trashed), ); case VideoControls.none: return const SizedBox(); diff --git a/lib/widgets/viewer/overlay/video/progress_bar.dart b/lib/widgets/viewer/overlay/video/progress_bar.dart index b96e26c10..aac65fe25 100644 --- a/lib/widgets/viewer/overlay/video/progress_bar.dart +++ b/lib/widgets/viewer/overlay/video/progress_bar.dart @@ -7,7 +7,7 @@ import 'package:aves/theme/themes.dart'; import 'package:aves/utils/constants.dart'; import 'package:aves/widgets/common/fx/blurred.dart'; import 'package:aves/widgets/common/fx/borders.dart'; -import 'package:aves/widgets/viewer/video/controller.dart'; +import 'package:aves_video/aves_video.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; diff --git a/lib/widgets/viewer/overlay/video/video.dart b/lib/widgets/viewer/overlay/video/video.dart index fa82c7fc1..409ea7bd2 100644 --- a/lib/widgets/viewer/overlay/video/video.dart +++ b/lib/widgets/viewer/overlay/video/video.dart @@ -1,11 +1,11 @@ import 'dart:async'; import 'package:aves/model/actions/entry_actions.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/widgets/common/identity/buttons/overlay_button.dart'; import 'package:aves/widgets/viewer/overlay/video/controls.dart'; import 'package:aves/widgets/viewer/overlay/video/progress_bar.dart'; -import 'package:aves/widgets/viewer/video/controller.dart'; +import 'package:aves_video/aves_video.dart'; import 'package:flutter/material.dart'; class VideoControlOverlay extends StatefulWidget { @@ -69,6 +69,7 @@ class _VideoControlOverlayState extends State with SingleTi ), ), VideoControlRow( + entry: entry, controller: controller, scale: scale, onActionSelected: widget.onActionSelected, diff --git a/lib/widgets/viewer/overlay/viewer_buttons.dart b/lib/widgets/viewer/overlay/viewer_buttons.dart index faf3196da..52a27e5db 100644 --- a/lib/widgets/viewer/overlay/viewer_buttons.dart +++ b/lib/widgets/viewer/overlay/viewer_buttons.dart @@ -2,7 +2,9 @@ import 'dart:math'; import 'package:aves/app_mode.dart'; import 'package:aves/model/actions/entry_actions.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/multipage.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/theme/durations.dart'; @@ -24,7 +26,7 @@ import 'package:aves/widgets/common/identity/buttons/overlay_button.dart'; import 'package:aves/widgets/viewer/action/entry_action_delegate.dart'; import 'package:aves/widgets/viewer/controls/notifications.dart'; import 'package:aves/widgets/viewer/video/conductor.dart'; -import 'package:aves/widgets/viewer/video/controller.dart'; +import 'package:aves_video/aves_video.dart'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/viewer/overlay/wallpaper_buttons.dart b/lib/widgets/viewer/overlay/wallpaper_buttons.dart index ba894d6e1..3d949f033 100644 --- a/lib/widgets/viewer/overlay/wallpaper_buttons.dart +++ b/lib/widgets/viewer/overlay/wallpaper_buttons.dart @@ -2,8 +2,9 @@ import 'dart:async'; import 'dart:math'; import 'dart:ui' as ui; -import 'package:aves/model/entry.dart'; -import 'package:aves/model/entry_images.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/images.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/wallpaper_target.dart'; import 'package:aves/services/wallpaper_service.dart'; import 'package:aves/widgets/common/action_mixins/feedback.dart'; diff --git a/lib/widgets/viewer/page_entry_builder.dart b/lib/widgets/viewer/page_entry_builder.dart index c3b25aca0..97fcb5db0 100644 --- a/lib/widgets/viewer/page_entry_builder.dart +++ b/lib/widgets/viewer/page_entry_builder.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/multipage.dart'; import 'package:aves/widgets/viewer/multipage/controller.dart'; import 'package:flutter/widgets.dart'; diff --git a/lib/widgets/viewer/panorama_page.dart b/lib/widgets/viewer/panorama_page.dart index ea1dbcd8f..00a97cb45 100644 --- a/lib/widgets/viewer/panorama_page.dart +++ b/lib/widgets/viewer/panorama_page.dart @@ -1,7 +1,7 @@ import 'dart:math'; -import 'package:aves/model/entry.dart'; -import 'package:aves/model/entry_images.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/images.dart'; import 'package:aves/model/panorama.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/icons.dart'; diff --git a/lib/widgets/viewer/slideshow_page.dart b/lib/widgets/viewer/slideshow_page.dart index c4762de49..48bcd0e2e 100644 --- a/lib/widgets/viewer/slideshow_page.dart +++ b/lib/widgets/viewer/slideshow_page.dart @@ -1,6 +1,6 @@ import 'package:aves/app_mode.dart'; import 'package:aves/model/actions/slideshow_actions.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/mime.dart'; import 'package:aves/model/settings/enums/enums.dart'; diff --git a/lib/widgets/viewer/video/conductor.dart b/lib/widgets/viewer/video/conductor.dart index 82abe32c3..7178949a4 100644 --- a/lib/widgets/viewer/video/conductor.dart +++ b/lib/widgets/viewer/video/conductor.dart @@ -1,11 +1,13 @@ import 'dart:async'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/services/common/services.dart'; -import 'package:aves/widgets/viewer/video/controller.dart'; +import 'package:aves_video/aves_video.dart'; +import 'package:aves/widgets/viewer/video/db_playback_state_handler.dart'; import 'package:aves/widgets/viewer/video/fijkplayer.dart'; import 'package:collection/collection.dart'; @@ -13,6 +15,7 @@ class VideoConductor { final CollectionLens? _collection; final List _controllers = []; final List _subscriptions = []; + final PlaybackStateHandler playbackStateHandler = DatabasePlaybackStateHandler(); static const _defaultMaxControllerCount = 3; @@ -34,8 +37,8 @@ class VideoConductor { if (controller != null) { _controllers.remove(controller); } else { - controller = IjkPlayerAvesVideoController(entry, persistPlayback: true); - _subscriptions.add(controller.statusStream.listen((event) => _onControllerStatusChanged(controller!, event))); + controller = IjkPlayerAvesVideoController(entry, playbackStateHandler: playbackStateHandler); + _subscriptions.add(controller.statusStream.listen((event) => _onControllerStatusChanged(entry, controller!, event))); } _controllers.insert(0, controller); while (_controllers.length > (maxControllerCount ?? _defaultMaxControllerCount)) { @@ -50,11 +53,11 @@ class VideoConductor { return _controllers.firstWhereOrNull((c) => c.entry.uri == entry.uri && c.entry.pageId == entry.pageId); } - Future _onControllerStatusChanged(AvesVideoController controller, VideoStatus status) async { + Future _onControllerStatusChanged(AvesEntry entry, AvesVideoController controller, VideoStatus status) async { bool canSkipToNext = false, canSkipToPrevious = false; final entries = _collection?.sortedEntries; if (entries != null) { - final currentIndex = entries.indexOf(controller.entry); + final currentIndex = entries.indexOf(entry); if (currentIndex != -1) { bool isVideo(AvesEntry entry) => entry.isVideo; canSkipToPrevious = entries.take(currentIndex).lastWhereOrNull(isVideo) != null; @@ -63,6 +66,7 @@ class VideoConductor { } await mediaSessionService.update( + entry: entry, controller: controller, canSkipToNext: canSkipToNext, canSkipToPrevious: canSkipToPrevious, diff --git a/lib/widgets/viewer/video/db_playback_state_handler.dart b/lib/widgets/viewer/video/db_playback_state_handler.dart new file mode 100644 index 000000000..675e25e1f --- /dev/null +++ b/lib/widgets/viewer/video/db_playback_state_handler.dart @@ -0,0 +1,58 @@ +import 'dart:async'; + +import 'package:aves/model/video_playback.dart'; +import 'package:aves/services/common/services.dart'; +import 'package:aves/theme/format.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/dialogs/aves_dialog.dart'; +import 'package:aves_video/aves_video.dart'; +import 'package:flutter/material.dart'; + +class DatabasePlaybackStateHandler extends PlaybackStateHandler { + static const resumeTimeSaveMinProgress = .05; + static const resumeTimeSaveMaxProgress = .95; + + @override + Future getResumeTime({required int entryId, required BuildContext context}) async { + final playback = await metadataDb.loadVideoPlayback(entryId); + final resumeTime = playback?.resumeTimeMillis ?? 0; + if (resumeTime == 0) return null; + + // clear on retrieval + await metadataDb.removeVideoPlayback({entryId}); + + final resume = await showDialog( + context: context, + builder: (context) => AvesDialog( + content: Text(context.l10n.videoResumeDialogMessage(formatFriendlyDuration(Duration(milliseconds: resumeTime)))), + actions: [ + TextButton( + onPressed: () => Navigator.maybeOf(context)?.pop(false), + child: Text(context.l10n.videoStartOverButtonLabel), + ), + TextButton( + onPressed: () => Navigator.maybeOf(context)?.pop(true), + child: Text(context.l10n.videoResumeButtonLabel), + ), + ], + ), + routeSettings: const RouteSettings(name: AvesDialog.confirmationRouteName), + ); + if (resume == null || !resume) return 0; + return resumeTime; + } + + @override + Future saveResumeTime({required int entryId, required int position, required double progress}) async { + if (resumeTimeSaveMinProgress < progress && progress < resumeTimeSaveMaxProgress) { + await metadataDb.addVideoPlayback({ + VideoPlaybackRow( + entryId: entryId, + resumeTimeMillis: position, + ) + }); + } else { + await metadataDb.removeVideoPlayback({entryId}); + } + } +} diff --git a/lib/widgets/viewer/video/fijkplayer.dart b/lib/widgets/viewer/video/fijkplayer.dart index 9b7d10a6f..2237ab41b 100644 --- a/lib/widgets/viewer/video/fijkplayer.dart +++ b/lib/widgets/viewer/video/fijkplayer.dart @@ -1,13 +1,13 @@ import 'dart:async'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/settings/enums/video_loop_mode.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/video/keys.dart'; import 'package:aves/model/video/metadata.dart'; import 'package:aves/services/common/optional_event_channel.dart'; import 'package:aves/utils/change_notifier.dart'; -import 'package:aves/widgets/viewer/video/controller.dart'; +import 'package:aves_video/aves_video.dart'; import 'package:collection/collection.dart'; import 'package:fijkplayer/fijkplayer.dart'; import 'package:flutter/material.dart'; @@ -62,12 +62,9 @@ class IjkPlayerAvesVideoController extends AvesVideoController { static const captureFrameEnabled = true; IjkPlayerAvesVideoController( - AvesEntry entry, { - required bool persistPlayback, - }) : super( - entry, - persistPlayback: persistPlayback, - ) { + super.entry, { + required super.playbackStateHandler, + }) { _instance = FijkPlayer(); _valueStream.map((value) => value.videoRenderStart).firstWhere((v) => v, orElse: () => false).then( (started) { @@ -168,7 +165,7 @@ class IjkPlayerAvesVideoController extends AvesVideoController { _macroBlockCrop = Offset(s.width, s.height); } - final loopEnabled = settings.videoLoopMode.shouldLoop(entry); + final loopEnabled = settings.videoLoopMode.shouldLoop(entry.durationMillis); // `fastseek`: enable fast, but inaccurate seeks for some formats // in practice the flag seems ineffective, but harmless too diff --git a/lib/widgets/viewer/video/flutter_vlc_player.dart b/lib/widgets/viewer/video/flutter_vlc_player.dart index d8f4dcaf4..df28f0ffc 100644 --- a/lib/widgets/viewer/video/flutter_vlc_player.dart +++ b/lib/widgets/viewer/video/flutter_vlc_player.dart @@ -3,7 +3,7 @@ // // import 'package:aves/model/entry.dart'; // import 'package:aves/utils/change_notifier.dart'; -// import 'package:aves/widgets/viewer/video/controller.dart'; +// import 'package:aves_video/aves_video.dart'; // import 'package:flutter/material.dart'; // import 'package:flutter/src/foundation/change_notifier.dart'; // import 'package:flutter/src/widgets/framework.dart'; diff --git a/lib/widgets/viewer/video/video_player.dart b/lib/widgets/viewer/video/video_player.dart index ee6d438c7..b3b4a663c 100644 --- a/lib/widgets/viewer/video/video_player.dart +++ b/lib/widgets/viewer/video/video_player.dart @@ -2,7 +2,7 @@ // // import 'package:aves/model/entry.dart'; // import 'package:aves/utils/change_notifier.dart'; -// import 'package:aves/widgets/viewer/video/controller.dart'; +// import 'package:aves_video/aves_video.dart'; // import 'package:flutter/src/foundation/change_notifier.dart'; // import 'package:flutter/src/widgets/framework.dart'; // import 'package:video_player/video_player.dart'; diff --git a/lib/widgets/viewer/visual/conductor.dart b/lib/widgets/viewer/visual/conductor.dart index d350c34ae..c6801c6a1 100644 --- a/lib/widgets/viewer/visual/conductor.dart +++ b/lib/widgets/viewer/visual/conductor.dart @@ -1,4 +1,5 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/widgets/viewer/visual/state.dart'; import 'package:aves_magnifier/aves_magnifier.dart'; import 'package:collection/collection.dart'; diff --git a/lib/widgets/viewer/visual/controller_mixin.dart b/lib/widgets/viewer/visual/controller_mixin.dart index f955fe8a2..6f39dd025 100644 --- a/lib/widgets/viewer/visual/controller_mixin.dart +++ b/lib/widgets/viewer/visual/controller_mixin.dart @@ -1,12 +1,14 @@ import 'package:aves/app_mode.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/multipage.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/widgets/viewer/multipage/conductor.dart'; import 'package:aves/widgets/viewer/multipage/controller.dart'; import 'package:aves/widgets/viewer/video/conductor.dart'; -import 'package:aves/widgets/viewer/video/controller.dart'; +import 'package:aves_video/aves_video.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; diff --git a/lib/widgets/viewer/visual/entry_page_view.dart b/lib/widgets/viewer/visual/entry_page_view.dart index 91af37de0..fff2d55d1 100644 --- a/lib/widgets/viewer/visual/entry_page_view.dart +++ b/lib/widgets/viewer/visual/entry_page_view.dart @@ -2,7 +2,8 @@ import 'dart:async'; import 'package:aves/app_mode.dart'; import 'package:aves/model/actions/entry_actions.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/services/common/services.dart'; @@ -233,6 +234,7 @@ class _EntryPageViewState extends State with SingleTickerProvider ); VideoActionNotification( controller: videoController, + entry: entry, action: action, ).dispatch(context); } @@ -326,6 +328,7 @@ class _EntryPageViewState extends State with SingleTickerProvider ), ), VideoSubtitles( + entry: entry, controller: videoController, viewStateNotifier: _viewStateNotifier, ), diff --git a/lib/widgets/viewer/visual/error.dart b/lib/widgets/viewer/visual/error.dart index 85ce3d3d9..50d8167ee 100644 --- a/lib/widgets/viewer/visual/error.dart +++ b/lib/widgets/viewer/visual/error.dart @@ -1,6 +1,6 @@ import 'dart:io'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/empty.dart'; diff --git a/lib/widgets/viewer/visual/raster.dart b/lib/widgets/viewer/visual/raster.dart index a7770966e..1924c3fad 100644 --- a/lib/widgets/viewer/visual/raster.dart +++ b/lib/widgets/viewer/visual/raster.dart @@ -1,8 +1,9 @@ import 'dart:math'; import 'package:aves/image_providers/region_provider.dart'; -import 'package:aves/model/entry.dart'; -import 'package:aves/model/entry_images.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/images.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/settings/enums/entry_background.dart'; import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/settings.dart'; diff --git a/lib/widgets/viewer/visual/vector.dart b/lib/widgets/viewer/visual/vector.dart index 02736fa30..b7eb3809c 100644 --- a/lib/widgets/viewer/visual/vector.dart +++ b/lib/widgets/viewer/visual/vector.dart @@ -2,8 +2,9 @@ import 'dart:math'; import 'dart:ui'; import 'package:aves/image_providers/region_provider.dart'; -import 'package:aves/model/entry.dart'; -import 'package:aves/model/entry_images.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/images.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/settings/enums/entry_background.dart'; import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/settings.dart'; diff --git a/lib/widgets/viewer/visual/video/cover.dart b/lib/widgets/viewer/visual/video/cover.dart index e9a3a81e1..6091cbd69 100644 --- a/lib/widgets/viewer/visual/video/cover.dart +++ b/lib/widgets/viewer/visual/video/cover.dart @@ -1,8 +1,9 @@ -import 'package:aves/model/entry.dart'; -import 'package:aves/model/entry_images.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/images.dart'; +import 'package:aves/model/entry/extensions/multipage.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/widgets/common/thumbnail/image.dart'; -import 'package:aves/widgets/viewer/video/controller.dart'; +import 'package:aves_video/aves_video.dart'; import 'package:aves_magnifier/aves_magnifier.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/viewer/visual/video/subtitle/subtitle.dart b/lib/widgets/viewer/visual/video/subtitle/subtitle.dart index c83f9db6f..48919273c 100644 --- a/lib/widgets/viewer/visual/video/subtitle/subtitle.dart +++ b/lib/widgets/viewer/visual/video/subtitle/subtitle.dart @@ -1,8 +1,10 @@ +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/settings/enums/subtitle_position.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/widgets/common/basic/text/background_painter.dart'; import 'package:aves/widgets/common/basic/text/outlined.dart'; -import 'package:aves/widgets/viewer/video/controller.dart'; +import 'package:aves_video/aves_video.dart'; import 'package:aves/widgets/viewer/visual/state.dart'; import 'package:aves/widgets/viewer/visual/video/subtitle/ass_parser.dart'; import 'package:aves/widgets/viewer/visual/video/subtitle/span.dart'; @@ -14,6 +16,7 @@ import 'package:latlong2/latlong.dart' as angles; import 'package:provider/provider.dart'; class VideoSubtitles extends StatelessWidget { + final AvesEntry entry; final AvesVideoController controller; final ValueNotifier viewStateNotifier; final bool debugMode; @@ -22,6 +25,7 @@ class VideoSubtitles extends StatelessWidget { const VideoSubtitles({ super.key, + required this.entry, required this.controller, required this.viewStateNotifier, this.debugMode = false, @@ -29,7 +33,7 @@ class VideoSubtitles extends StatelessWidget { @override Widget build(BuildContext context) { - final videoDisplaySize = controller.entry.videoDisplaySize(controller.sarNotifier.value); + final videoDisplaySize = entry.videoDisplaySize(controller.sarNotifier.value); return IgnorePointer( child: Consumer( builder: (context, settings, child) { diff --git a/lib/widgets/viewer/visual/video/video_view.dart b/lib/widgets/viewer/visual/video/video_view.dart index 6fadf9908..06c3d6819 100644 --- a/lib/widgets/viewer/visual/video/video_view.dart +++ b/lib/widgets/viewer/visual/video/video_view.dart @@ -1,5 +1,5 @@ -import 'package:aves/model/entry.dart'; -import 'package:aves/widgets/viewer/video/controller.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves_video/aves_video.dart'; import 'package:flutter/material.dart'; class VideoView extends StatefulWidget { diff --git a/lib/widgets/wallpaper_page.dart b/lib/widgets/wallpaper_page.dart index 5d6ad023a..7d398f5d3 100644 --- a/lib/widgets/wallpaper_page.dart +++ b/lib/widgets/wallpaper_page.dart @@ -1,5 +1,7 @@ import 'package:aves/model/actions/entry_actions.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/multipage.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/services/common/services.dart'; @@ -17,9 +19,9 @@ import 'package:aves/widgets/viewer/overlay/video/video.dart'; import 'package:aves/widgets/viewer/page_entry_builder.dart'; import 'package:aves/widgets/viewer/providers.dart'; import 'package:aves/widgets/viewer/video/conductor.dart'; -import 'package:aves/widgets/viewer/video/controller.dart'; import 'package:aves/widgets/viewer/visual/controller_mixin.dart'; import 'package:aves_magnifier/aves_magnifier.dart'; +import 'package:aves_video/aves_video.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:screen_brightness/screen_brightness.dart'; @@ -132,9 +134,12 @@ class _EntryEditorState extends State with EntryViewControllerMixin if (notification is ToggleOverlayNotification) { _overlayVisible.value = notification.visible ?? !_overlayVisible.value; } else if (notification is VideoActionNotification) { - final controller = notification.controller; - final action = notification.action; - _onVideoAction(context, controller, action); + _onVideoAction( + context: context, + entry: notification.entry, + controller: notification.controller, + action: notification.action, + ); } return true; }, @@ -176,7 +181,12 @@ class _EntryEditorState extends State with EntryViewControllerMixin entry: targetEntry, controller: videoController, scale: _overlayVideoControlScale, - onActionSelected: (action) => _onVideoAction(context, videoController, action), + onActionSelected: (action) => _onVideoAction( + context: context, + entry: targetEntry, + controller: videoController, + action: action, + ), onActionMenuOpened: () { // if the menu is opened while overlay is hiding, // the popup menu button is disposed and menu items are ineffective, @@ -236,9 +246,14 @@ class _EntryEditorState extends State with EntryViewControllerMixin ); } - void _onVideoAction(BuildContext context, AvesVideoController? videoController, EntryAction action) { - if (videoController != null) { - _videoActionDelegate.onActionSelected(context, videoController, action); + void _onVideoAction({ + required BuildContext context, + required AvesEntry entry, + required AvesVideoController? controller, + required EntryAction action, + }) { + if (controller != null) { + _videoActionDelegate.onActionSelected(context, entry, controller, action); } } diff --git a/plugins/aves_model/.gitignore b/plugins/aves_model/.gitignore new file mode 100644 index 000000000..28124a571 --- /dev/null +++ b/plugins/aves_model/.gitignore @@ -0,0 +1,30 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +#/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/plugins/aves_model/.metadata b/plugins/aves_model/.metadata new file mode 100644 index 000000000..ad910f56c --- /dev/null +++ b/plugins/aves_model/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 2ad6cd72c040113b47ee9055e722606a490ef0da + channel: stable + +project_type: package diff --git a/plugins/aves_model/analysis_options.yaml b/plugins/aves_model/analysis_options.yaml new file mode 100644 index 000000000..a5744c1cf --- /dev/null +++ b/plugins/aves_model/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/plugins/aves_model/lib/aves_model.dart b/plugins/aves_model/lib/aves_model.dart new file mode 100644 index 000000000..4f604ce32 --- /dev/null +++ b/plugins/aves_model/lib/aves_model.dart @@ -0,0 +1,3 @@ +library aves_model; + +export 'src/entry_base.dart'; diff --git a/plugins/aves_model/lib/src/entry_base.dart b/plugins/aves_model/lib/src/entry_base.dart new file mode 100644 index 000000000..01966327d --- /dev/null +++ b/plugins/aves_model/lib/src/entry_base.dart @@ -0,0 +1,23 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; + +mixin AvesEntryBase { + int get id; + + String get uri; + + int? get pageId; + + int? get sizeBytes; + + int? get durationMillis; + + int get rotationDegrees; + + Size get displaySize; + + double get displayAspectRatio; + + Listenable get visualChangeNotifier; +} diff --git a/plugins/aves_model/pubspec.lock b/plugins/aves_model/pubspec.lock new file mode 100644 index 000000000..6e38c33fc --- /dev/null +++ b/plugins/aves_model/pubspec.lock @@ -0,0 +1,79 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + characters: + dependency: transitive + description: + name: characters + sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + url: "https://pub.dev" + source: hosted + version: "1.2.1" + collection: + dependency: transitive + description: + name: collection + sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + url: "https://pub.dev" + source: hosted + version: "1.17.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + url: "https://pub.dev" + source: hosted + version: "2.0.1" + js: + dependency: transitive + description: + name: js + sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + url: "https://pub.dev" + source: hosted + version: "0.6.5" + lints: + dependency: transitive + description: + name: lints + sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + meta: + dependency: transitive + description: + name: meta + sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + url: "https://pub.dev" + source: hosted + version: "1.8.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" +sdks: + dart: ">=2.19.4 <3.0.0" diff --git a/plugins/aves_model/pubspec.yaml b/plugins/aves_model/pubspec.yaml new file mode 100644 index 000000000..440c3befc --- /dev/null +++ b/plugins/aves_model/pubspec.yaml @@ -0,0 +1,15 @@ +name: aves_model +version: 0.0.1 +publish_to: none + +environment: + sdk: '>=2.19.4 <3.0.0' + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_lints: + +flutter: diff --git a/plugins/aves_video/.gitignore b/plugins/aves_video/.gitignore new file mode 100644 index 000000000..28124a571 --- /dev/null +++ b/plugins/aves_video/.gitignore @@ -0,0 +1,30 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +#/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/plugins/aves_video/.metadata b/plugins/aves_video/.metadata new file mode 100644 index 000000000..ad910f56c --- /dev/null +++ b/plugins/aves_video/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 2ad6cd72c040113b47ee9055e722606a490ef0da + channel: stable + +project_type: package diff --git a/plugins/aves_video/analysis_options.yaml b/plugins/aves_video/analysis_options.yaml new file mode 100644 index 000000000..f04c6cf0f --- /dev/null +++ b/plugins/aves_video/analysis_options.yaml @@ -0,0 +1 @@ +include: ../../analysis_options.yaml diff --git a/plugins/aves_video/lib/aves_video.dart b/plugins/aves_video/lib/aves_video.dart new file mode 100644 index 000000000..90e236285 --- /dev/null +++ b/plugins/aves_video/lib/aves_video.dart @@ -0,0 +1,3 @@ +library aves_video; + +export 'src/controller.dart'; diff --git a/lib/widgets/viewer/video/controller.dart b/plugins/aves_video/lib/src/controller.dart similarity index 52% rename from lib/widgets/viewer/video/controller.dart rename to plugins/aves_video/lib/src/controller.dart index a41fb6d01..94330e789 100644 --- a/lib/widgets/viewer/video/controller.dart +++ b/plugins/aves_video/lib/src/controller.dart @@ -1,84 +1,33 @@ import 'dart:async'; -import 'package:aves/model/entry.dart'; -import 'package:aves/model/video_playback.dart'; -import 'package:aves/services/common/services.dart'; -import 'package:aves/theme/format.dart'; -import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:aves/widgets/dialogs/aves_dialog.dart'; +import 'package:aves_model/aves_model.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; abstract class AvesVideoController { - final AvesEntry _entry; - final bool persistPlayback; + final AvesEntryBase _entry; + final PlaybackStateHandler playbackStateHandler; - AvesEntry get entry => _entry; + AvesEntryBase get entry => _entry; - static const resumeTimeSaveMinProgress = .05; - static const resumeTimeSaveMaxProgress = .95; static const resumeTimeSaveMinDuration = Duration(minutes: 2); - AvesVideoController(AvesEntry entry, {required this.persistPlayback}) : _entry = entry { + AvesVideoController(AvesEntryBase entry, {required this.playbackStateHandler}) : _entry = entry { entry.visualChangeNotifier.addListener(onVisualChanged); } @mustCallSuper Future dispose() async { - entry.visualChangeNotifier.removeListener(onVisualChanged); + _entry.visualChangeNotifier.removeListener(onVisualChanged); await _savePlaybackState(); } Future _savePlaybackState() async { - final id = entry.id; if (!isReady || duration < resumeTimeSaveMinDuration.inMilliseconds) return; - - if (persistPlayback) { - final _progress = progress; - if (resumeTimeSaveMinProgress < _progress && _progress < resumeTimeSaveMaxProgress) { - await metadataDb.addVideoPlayback({ - VideoPlaybackRow( - entryId: id, - resumeTimeMillis: currentPosition, - ) - }); - } else { - await metadataDb.removeVideoPlayback({id}); - } - } + await playbackStateHandler.saveResumeTime(entryId: _entry.id, position: currentPosition, progress: progress); } - Future getResumeTime(BuildContext context) async { - if (!persistPlayback) return null; - - final id = entry.id; - final playback = await metadataDb.loadVideoPlayback(id); - final resumeTime = playback?.resumeTimeMillis ?? 0; - if (resumeTime == 0) return null; - - // clear on retrieval - await metadataDb.removeVideoPlayback({id}); - - final resume = await showDialog( - context: context, - builder: (context) => AvesDialog( - content: Text(context.l10n.videoResumeDialogMessage(formatFriendlyDuration(Duration(milliseconds: resumeTime)))), - actions: [ - TextButton( - onPressed: () => Navigator.maybeOf(context)?.pop(false), - child: Text(context.l10n.videoStartOverButtonLabel), - ), - TextButton( - onPressed: () => Navigator.maybeOf(context)?.pop(true), - child: Text(context.l10n.videoResumeButtonLabel), - ), - ], - ), - routeSettings: const RouteSettings(name: AvesDialog.confirmationRouteName), - ); - if (resume == null || !resume) return 0; - return resumeTime; - } + Future getResumeTime(BuildContext context) => playbackStateHandler.getResumeTime(entryId: _entry.id, context: context); void onVisualChanged(); @@ -179,3 +128,9 @@ class StreamSummary { @override String toString() => '$runtimeType#${shortHash(this)}{type: type, index: $index, codecName: $codecName, language: $language, title: $title, width: $width, height: $height}'; } + +abstract class PlaybackStateHandler { + Future getResumeTime({required int entryId, required BuildContext context}); + + Future saveResumeTime({required int entryId, required int position, required double progress}); +} diff --git a/plugins/aves_video/pubspec.lock b/plugins/aves_video/pubspec.lock new file mode 100644 index 000000000..1e86a4ad9 --- /dev/null +++ b/plugins/aves_video/pubspec.lock @@ -0,0 +1,94 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + aves_model: + dependency: "direct main" + description: + path: "../aves_model" + relative: true + source: path + version: "0.0.1" + characters: + dependency: transitive + description: + name: characters + sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + url: "https://pub.dev" + source: hosted + version: "1.2.1" + collection: + dependency: transitive + description: + name: collection + sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + url: "https://pub.dev" + source: hosted + version: "1.17.0" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + url: "https://pub.dev" + source: hosted + version: "2.0.1" + js: + dependency: transitive + description: + name: js + sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + url: "https://pub.dev" + source: hosted + version: "0.6.5" + lints: + dependency: transitive + description: + name: lints + sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + meta: + dependency: transitive + description: + name: meta + sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + url: "https://pub.dev" + source: hosted + version: "1.8.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" +sdks: + dart: ">=2.19.4 <3.0.0" diff --git a/plugins/aves_video/pubspec.yaml b/plugins/aves_video/pubspec.yaml new file mode 100644 index 000000000..c4362c548 --- /dev/null +++ b/plugins/aves_video/pubspec.yaml @@ -0,0 +1,17 @@ +name: aves_video +version: 0.0.1 +publish_to: none + +environment: + sdk: '>=2.19.4 <3.0.0' + +dependencies: + flutter: + sdk: flutter + aves_model: + path: ../aves_model + +dev_dependencies: + flutter_lints: + +flutter: diff --git a/pubspec.lock b/pubspec.lock index 614344212..c894bb047 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -63,6 +63,13 @@ packages: relative: true source: path version: "0.0.1" + aves_model: + dependency: "direct main" + description: + path: "plugins/aves_model" + relative: true + source: path + version: "0.0.1" aves_report: dependency: "direct main" description: @@ -98,6 +105,13 @@ packages: relative: true source: path version: "0.0.1" + aves_video: + dependency: "direct main" + description: + path: "plugins/aves_video" + relative: true + source: path + version: "0.0.1" barcode: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c091321ea..74b0dc351 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,6 +29,8 @@ dependencies: path: plugins/aves_magnifier aves_map: path: plugins/aves_map + aves_model: + path: plugins/aves_model aves_report: path: plugins/aves_report aves_report_platform: @@ -37,6 +39,8 @@ dependencies: path: plugins/aves_services aves_services_platform: path: plugins/aves_services_google + aves_video: + path: plugins/aves_video aves_ui: path: plugins/aves_ui charts_flutter: diff --git a/test/fake/media_fetch_service.dart b/test/fake/media_fetch_service.dart index fa9645ace..50dd1d969 100644 --- a/test/fake/media_fetch_service.dart +++ b/test/fake/media_fetch_service.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/services/media/media_fetch_service.dart'; import 'package:collection/collection.dart'; import 'package:test/fake.dart'; diff --git a/test/fake/media_store_service.dart b/test/fake/media_store_service.dart index eb9082ee2..d2f2ba4ef 100644 --- a/test/fake/media_store_service.dart +++ b/test/fake/media_store_service.dart @@ -1,4 +1,5 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/origins.dart'; import 'package:aves/ref/mime_types.dart'; import 'package:aves/services/common/image_op_events.dart'; import 'package:aves/services/media/media_store_service.dart'; diff --git a/test/fake/metadata_db.dart b/test/fake/metadata_db.dart index 408ac2c3c..9f38ee70e 100644 --- a/test/fake/metadata_db.dart +++ b/test/fake/metadata_db.dart @@ -1,6 +1,6 @@ import 'package:aves/model/covers.dart'; import 'package:aves/model/db/db_metadata.dart'; -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/favourites.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/metadata/address.dart'; diff --git a/test/fake/metadata_fetch_service.dart b/test/fake/metadata_fetch_service.dart index 1891146ea..125ac4d6e 100644 --- a/test/fake/metadata_fetch_service.dart +++ b/test/fake/metadata_fetch_service.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/metadata/catalog.dart'; import 'package:aves/services/metadata/metadata_fetch_service.dart'; import 'package:flutter/foundation.dart'; diff --git a/test/model/collection_source_test.dart b/test/model/collection_source_test.dart index 20edbce4c..ecc7127af 100644 --- a/test/model/collection_source_test.dart +++ b/test/model/collection_source_test.dart @@ -4,6 +4,7 @@ import 'package:aves/model/actions/move_type.dart'; import 'package:aves/model/availability.dart'; import 'package:aves/model/covers.dart'; import 'package:aves/model/db/db_metadata.dart'; +import 'package:aves/model/entry/extensions/favourites.dart'; import 'package:aves/model/favourites.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/tag.dart'; diff --git a/test/utils/xmp_utils_test.dart b/test/utils/xmp_utils_test.dart index 489ee1677..84197b9c1 100644 --- a/test/utils/xmp_utils_test.dart +++ b/test/utils/xmp_utils_test.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry_metadata_edition.dart'; +import 'package:aves/model/entry/extensions/metadata_edition.dart'; import 'package:aves/utils/xmp_utils.dart'; import 'package:test/test.dart'; import 'package:xml/xml.dart'; From 119968439a5b892da2f6fc77f42fe5153d0b1f9c Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Tue, 14 Mar 2023 20:31:34 +0100 Subject: [PATCH 03/11] refactor --- lib/model/entry_extensions/geo.dart | 0 lib/model/entry_extensions/images.dart | 79 --- lib/model/entry_extensions/info.dart | 158 ----- .../entry_extensions/metadata_edition.dart | 587 ------------------ lib/model/entry_extensions/multipage.dart | 0 5 files changed, 824 deletions(-) delete mode 100644 lib/model/entry_extensions/geo.dart delete mode 100644 lib/model/entry_extensions/images.dart delete mode 100644 lib/model/entry_extensions/info.dart delete mode 100644 lib/model/entry_extensions/metadata_edition.dart delete mode 100644 lib/model/entry_extensions/multipage.dart diff --git a/lib/model/entry_extensions/geo.dart b/lib/model/entry_extensions/geo.dart deleted file mode 100644 index e69de29bb..000000000 diff --git a/lib/model/entry_extensions/images.dart b/lib/model/entry_extensions/images.dart deleted file mode 100644 index 95a3e24a4..000000000 --- a/lib/model/entry_extensions/images.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'dart:math'; - -import 'package:aves/image_providers/region_provider.dart'; -import 'package:aves/image_providers/thumbnail_provider.dart'; -import 'package:aves/image_providers/uri_image_provider.dart'; -import 'package:aves/model/entry.dart'; -import 'package:aves/model/entry_cache.dart'; -import 'package:aves/utils/math_utils.dart'; -import 'package:collection/collection.dart'; -import 'package:flutter/widgets.dart'; - -extension ExtraAvesEntryImages on AvesEntry { - bool isThumbnailReady({double extent = 0}) => _isReady(_getThumbnailProviderKey(extent)); - - ThumbnailProvider getThumbnail({double extent = 0}) { - return ThumbnailProvider(_getThumbnailProviderKey(extent)); - } - - ThumbnailProviderKey _getThumbnailProviderKey(double extent) { - EntryCache.markThumbnailExtent(extent); - return ThumbnailProviderKey( - uri: uri, - mimeType: mimeType, - pageId: pageId, - rotationDegrees: rotationDegrees, - isFlipped: isFlipped, - dateModifiedSecs: dateModifiedSecs ?? -1, - extent: extent, - ); - } - - RegionProvider getRegion({int sampleSize = 1, double scale = 1, required Rectangle region}) { - return RegionProvider(RegionProviderKey( - uri: uri, - mimeType: mimeType, - pageId: pageId, - sizeBytes: sizeBytes, - rotationDegrees: rotationDegrees, - isFlipped: isFlipped, - sampleSize: sampleSize, - region: Rectangle( - (region.left * scale).round(), - (region.top * scale).round(), - (region.width * scale).round(), - (region.height * scale).round(), - ), - imageSize: Size((width * scale).toDouble(), (height * scale).toDouble()), - )); - } - - UriImage get uriImage => UriImage( - uri: uri, - mimeType: mimeType, - pageId: pageId, - rotationDegrees: rotationDegrees, - isFlipped: isFlipped, - sizeBytes: sizeBytes, - ); - - bool _isReady(Object providerKey) => imageCache.statusForKey(providerKey).keepAlive; - - List get cachedThumbnails => EntryCache.thumbnailRequestExtents.map(_getThumbnailProviderKey).where(_isReady).map(ThumbnailProvider.new).toList(); - - ThumbnailProvider get bestCachedThumbnail { - final sizedThumbnailKey = EntryCache.thumbnailRequestExtents.map(_getThumbnailProviderKey).firstWhereOrNull(_isReady); - return sizedThumbnailKey != null ? ThumbnailProvider(sizedThumbnailKey) : getThumbnail(); - } - - // magic number used to derive sample size from scale - static const scaleFactor = 2.0; - - static int sampleSizeForScale(double scale) { - var sample = 0; - if (0 < scale && scale < 1) { - sample = highestPowerOf2((1 / scale) / scaleFactor); - } - return max(1, sample); - } -} diff --git a/lib/model/entry_extensions/info.dart b/lib/model/entry_extensions/info.dart deleted file mode 100644 index 4589b5f6f..000000000 --- a/lib/model/entry_extensions/info.dart +++ /dev/null @@ -1,158 +0,0 @@ -import 'dart:async'; -import 'dart:collection'; - -import 'package:aves/model/entry.dart'; -import 'package:aves/model/video/keys.dart'; -import 'package:aves/model/video/metadata.dart'; -import 'package:aves/ref/mime_types.dart'; -import 'package:aves/services/common/services.dart'; -import 'package:aves/services/metadata/svg_metadata_service.dart'; -import 'package:aves/theme/colors.dart'; -import 'package:aves/utils/constants.dart'; -import 'package:aves/widgets/viewer/info/metadata/metadata_dir.dart'; -import 'package:collection/collection.dart'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -extension ExtraAvesEntryInfo on AvesEntry { - // directory names may contain the name of their parent directory (as prefix + '/') - // directory names may contain an index (as suffix in '[]') - static final directoryNamePattern = RegExp(r'^((?.*?)/)?(?.*?)(\[(?\d+)\])?$'); - - Future>> getMetadataDirectories(BuildContext context) async { - final rawMetadata = await (isSvg ? SvgMetadataService.getAllMetadata(this) : metadataFetchService.getAllMetadata(this)); - final directories = rawMetadata.entries.map((dirKV) { - var directoryName = dirKV.key as String; - - String? parent; - int? index; - final match = directoryNamePattern.firstMatch(directoryName); - if (match != null) { - parent = match.namedGroup('parent'); - final nameMatch = match.namedGroup('name'); - if (nameMatch != null) { - directoryName = nameMatch; - } - final indexMatch = match.namedGroup('index'); - if (indexMatch != null) { - index = int.tryParse(indexMatch); - } - } - - final rawTags = dirKV.value as Map; - return MetadataDirectory( - directoryName, - _toSortedTags(rawTags), - parent: parent, - index: index, - ); - }).toList(); - - if (isVideo || (mimeType == MimeTypes.heif && isMultiPage)) { - directories.addAll(await _getStreamDirectories(context)); - } - - final titledDirectories = directories.map((dir) { - var title = dir.name; - if (directories.where((dir) => dir.name == title).length > 1 && dir.parent?.isNotEmpty == true) { - title = '${dir.parent}/$title'; - } - if (dir.index != null) { - title += ' ${dir.index}'; - } - return MapEntry(title, dir); - }).toList() - ..sort((a, b) => compareAsciiUpperCase(a.key, b.key)); - - return titledDirectories; - } - - Future> _getStreamDirectories(BuildContext context) async { - final directories = []; - final mediaInfo = await VideoMetadataFormatter.getVideoMetadata(this); - - final formattedMediaTags = VideoMetadataFormatter.formatInfo(mediaInfo); - if (formattedMediaTags.isNotEmpty) { - // overwrite generic directory found from the platform side - directories.add(MetadataDirectory(MetadataDirectory.mediaDirectory, _toSortedTags(formattedMediaTags))); - } - - if (mediaInfo.containsKey(Keys.streams)) { - String getTypeText(Map stream) { - final type = stream[Keys.streamType] ?? StreamTypes.unknown; - switch (type) { - case StreamTypes.attachment: - return 'Attachment'; - case StreamTypes.audio: - return 'Audio'; - case StreamTypes.metadata: - return 'Metadata'; - case StreamTypes.subtitle: - case StreamTypes.timedText: - return 'Text'; - case StreamTypes.video: - return stream.containsKey(Keys.fpsDen) ? 'Video' : 'Image'; - case StreamTypes.unknown: - default: - return 'Unknown'; - } - } - - final allStreams = (mediaInfo[Keys.streams] as List).cast(); - final attachmentStreams = allStreams.where((stream) => stream[Keys.streamType] == StreamTypes.attachment).toList(); - final knownStreams = allStreams.whereNot(attachmentStreams.contains); - - // display known streams as separate directories (e.g. video, audio, subs) - if (knownStreams.isNotEmpty) { - final indexDigits = knownStreams.length.toString().length; - - final colors = context.read(); - for (final stream in knownStreams) { - final index = (stream[Keys.index] ?? 0) + 1; - final typeText = getTypeText(stream); - final dirName = [ - 'Stream ${index.toString().padLeft(indexDigits, '0')}', - typeText, - ].join(Constants.separator); - final formattedStreamTags = VideoMetadataFormatter.formatInfo(stream); - if (formattedStreamTags.isNotEmpty) { - final color = colors.fromString(typeText); - directories.add(MetadataDirectory(dirName, _toSortedTags(formattedStreamTags), color: color)); - } - } - } - - // group attachments by format (e.g. TTF fonts) - if (attachmentStreams.isNotEmpty) { - final formatCount = >{}; - for (final stream in attachmentStreams) { - final codec = (stream[Keys.codecName] as String? ?? 'unknown').toUpperCase(); - if (!formatCount.containsKey(codec)) { - formatCount[codec] = []; - } - formatCount[codec]!.add(stream[Keys.filename]); - } - if (formatCount.isNotEmpty) { - final rawTags = formatCount.map((key, value) { - final count = value.length; - // remove duplicate names, so number of displayed names may not match displayed count - final names = value.whereNotNull().toSet().toList()..sort(compareAsciiUpperCase); - return MapEntry(key, '$count items: ${names.join(', ')}'); - }); - directories.add(MetadataDirectory('Attachments', _toSortedTags(rawTags))); - } - } - } - return directories; - } - - SplayTreeMap _toSortedTags(Map rawTags) { - final tags = SplayTreeMap.of(Map.fromEntries(rawTags.entries.map((tagKV) { - var value = (tagKV.value as String? ?? '').trim(); - if (value.isEmpty) return null; - final tagName = tagKV.key as String; - return MapEntry(tagName, value); - }).whereNotNull())); - return tags; - } -} diff --git a/lib/model/entry_extensions/metadata_edition.dart b/lib/model/entry_extensions/metadata_edition.dart deleted file mode 100644 index 6a0eb8ff3..000000000 --- a/lib/model/entry_extensions/metadata_edition.dart +++ /dev/null @@ -1,587 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:aves/model/entry.dart'; -import 'package:aves/model/metadata/date_modifier.dart'; -import 'package:aves/model/metadata/enums/date_field_source.dart'; -import 'package:aves/model/metadata/enums/enums.dart'; -import 'package:aves/model/metadata/fields.dart'; -import 'package:aves/ref/exif.dart'; -import 'package:aves/ref/iptc.dart'; -import 'package:aves/ref/mime_types.dart'; -import 'package:aves/services/common/services.dart'; -import 'package:aves/services/metadata/xmp.dart'; -import 'package:aves/utils/time_utils.dart'; -import 'package:aves/utils/xmp_utils.dart'; -import 'package:flutter/foundation.dart'; -import 'package:intl/intl.dart'; -import 'package:latlong2/latlong.dart'; -import 'package:package_info_plus/package_info_plus.dart'; -import 'package:xml/xml.dart'; - -extension ExtraAvesEntryMetadataEdition on AvesEntry { - Future> editDate(DateModifier userModifier) async { - final dataTypes = {}; - - final appliedModifier = await _applyDateModifierToEntry(userModifier); - if (appliedModifier == null) { - if (!isMissingAtPath && userModifier.action != DateEditAction.copyField) { - await reportService.recordError('failed to get date for modifier=$userModifier, entry=$this', null); - } - return {}; - } - - if (canEditExif && appliedModifier.fields.any((v) => v.type == MetadataType.exif)) { - final newFields = await metadataEditService.editExifDate(this, appliedModifier); - if (newFields.isNotEmpty) { - dataTypes.addAll({ - EntryDataType.basic, - EntryDataType.catalog, - }); - } - } - - if (canEditXmp && appliedModifier.fields.any((v) => v.type == MetadataType.xmp)) { - final metadata = { - MetadataType.xmp: await _editXmp((descriptions) { - switch (appliedModifier.action) { - case DateEditAction.setCustom: - case DateEditAction.copyField: - case DateEditAction.copyItem: - case DateEditAction.extractFromTitle: - editCreateDateXmp(descriptions, appliedModifier.setDateTime); - break; - case DateEditAction.shift: - final xmpDate = XMP.getString(descriptions, XMP.xmpCreateDate, namespace: Namespaces.xmp); - if (xmpDate != null) { - final date = DateTime.tryParse(xmpDate); - if (date != null) { - // TODO TLAD [date] DateTime.tryParse converts to UTC time, losing the time zone offset - final shiftedDate = date.add(Duration(minutes: appliedModifier.shiftMinutes!)); - editCreateDateXmp(descriptions, shiftedDate); - } else { - reportService.recordError('failed to parse XMP date=$xmpDate', null); - } - } - break; - case DateEditAction.remove: - editCreateDateXmp(descriptions, null); - break; - } - return true; - }), - }; - final newFields = await metadataEditService.editMetadata(this, metadata); - if (newFields.isNotEmpty) { - dataTypes.addAll({ - EntryDataType.basic, - EntryDataType.catalog, - }); - } - } - - return dataTypes; - } - - static final removalLocation = LatLng(0, 0); - - Future> editLocation(LatLng? latLng) async { - final dataTypes = {}; - final metadata = {}; - - final missingDate = await _missingDateCheckAndExifEdit(dataTypes); - - if (canEditExif) { - // clear every GPS field - final exifFields = Map.fromEntries(MetadataFields.exifGpsFields.map((k) => MapEntry(k, null))); - // add latitude & longitude, if any - if (latLng != null && latLng != removalLocation) { - final latitude = latLng.latitude; - final longitude = latLng.longitude; - exifFields.addAll({ - MetadataField.exifGpsLatitude: latitude.abs(), - MetadataField.exifGpsLatitudeRef: latitude >= 0 ? Exif.latitudeNorth : Exif.latitudeSouth, - MetadataField.exifGpsLongitude: longitude.abs(), - MetadataField.exifGpsLongitudeRef: longitude >= 0 ? Exif.longitudeEast : Exif.longitudeWest, - }); - } - metadata[MetadataType.exif] = Map.fromEntries(exifFields.entries.map((kv) => MapEntry(kv.key.toPlatform!, kv.value))); - - if (canEditXmp && missingDate != null) { - metadata[MetadataType.xmp] = await _editXmp((descriptions) { - editCreateDateXmp(descriptions, missingDate); - return true; - }); - } - } - - if (mimeType == MimeTypes.mp4) { - final mp4Fields = {}; - - String? iso6709String; - if (latLng != null && latLng != removalLocation) { - final latitude = latLng.latitude; - final longitude = latLng.longitude; - const locale = 'en_US'; - final isoLat = '${latitude >= 0 ? '+' : '-'}${NumberFormat('00.0000', locale).format(latitude.abs())}'; - final isoLon = '${longitude >= 0 ? '+' : '-'}${NumberFormat('000.0000', locale).format(longitude.abs())}'; - iso6709String = '$isoLat$isoLon/'; - } - mp4Fields[MetadataField.mp4GpsCoordinates] = iso6709String; - - if (missingDate != null) { - final xmpParts = await _editXmp((descriptions) { - editCreateDateXmp(descriptions, missingDate); - return true; - }); - mp4Fields[MetadataField.mp4Xmp] = xmpParts[xmpCoreKey]; - } - - metadata[MetadataType.mp4] = Map.fromEntries(mp4Fields.entries.map((kv) => MapEntry(kv.key.toPlatform!, kv.value))); - } - - final newFields = await metadataEditService.editMetadata(this, metadata); - if (newFields.isNotEmpty) { - dataTypes.addAll({ - EntryDataType.catalog, - EntryDataType.address, - }); - } - return dataTypes; - } - - Future> _changeExifOrientation(Future> Function() apply) async { - final dataTypes = {}; - - await _missingDateCheckAndExifEdit(dataTypes); - - final newFields = await apply(); - // applying fields is only useful for a smoother visual change, - // as proper refreshing and persistence happens at the caller level - await applyNewFields(newFields, persist: false); - if (newFields.isNotEmpty) { - dataTypes.addAll({ - EntryDataType.basic, - EntryDataType.aspectRatio, - EntryDataType.catalog, - }); - } - return dataTypes; - } - - Future> _rotateMp4(int rotationDegrees) async { - final dataTypes = {}; - - final missingDate = await _missingDateCheckAndExifEdit(dataTypes); - - final mp4Fields = { - MetadataField.mp4RotationDegrees: rotationDegrees.toString(), - }; - - if (missingDate != null) { - final xmpParts = await _editXmp((descriptions) { - editCreateDateXmp(descriptions, missingDate); - return true; - }); - mp4Fields[MetadataField.mp4Xmp] = xmpParts[xmpCoreKey]; - } - - final metadata = { - MetadataType.mp4: Map.fromEntries(mp4Fields.entries.map((kv) => MapEntry(kv.key.toPlatform!, kv.value))), - }; - - final newFields = await metadataEditService.editMetadata(this, metadata); - // applying fields is only useful for a smoother visual change, - // as proper refreshing and persistence happens at the caller level - await applyNewFields(newFields, persist: false); - if (newFields.isNotEmpty) { - dataTypes.addAll({ - EntryDataType.basic, - EntryDataType.aspectRatio, - EntryDataType.catalog, - }); - } - return dataTypes; - } - - Future> rotate({required bool clockwise}) { - if (mimeType == MimeTypes.mp4) { - return _rotateMp4((rotationDegrees + (clockwise ? 90 : -90) + 360) % 360); - } else { - return _changeExifOrientation(() => metadataEditService.rotate(this, clockwise: clockwise)); - } - } - - Future> flip() { - return _changeExifOrientation(() => metadataEditService.flip(this)); - } - - // write title: - // - IPTC / object-name, if IPTC exists - // - XMP / dc:title - // write description: - // - Exif / ImageDescription - // - IPTC / caption-abstract, if IPTC exists - // - XMP / dc:description - Future> editTitleDescription(Map fields) async { - final dataTypes = {}; - final metadata = {}; - - final missingDate = await _missingDateCheckAndExifEdit(dataTypes); - - final editTitle = fields.keys.contains(DescriptionField.title); - final editDescription = fields.keys.contains(DescriptionField.description); - final title = fields[DescriptionField.title]; - final description = fields[DescriptionField.description]; - - if (canEditExif && editDescription) { - metadata[MetadataType.exif] = { - MetadataField.exifImageDescription.toPlatform!: null, - MetadataField.exifUserComment.toPlatform!: null, - }; - } - - if (canEditIptc) { - final iptc = await metadataFetchService.getIptc(this); - if (iptc != null) { - if (editTitle) { - editIptcValues(iptc, IPTC.applicationRecord, IPTC.objectName, {if (title != null) title}); - } - if (editDescription) { - editIptcValues(iptc, IPTC.applicationRecord, IPTC.captionAbstractTag, {if (description != null) description}); - } - metadata[MetadataType.iptc] = iptc; - } - } - - if (canEditXmp) { - metadata[MetadataType.xmp] = await _editXmp((descriptions) { - var modified = false; - if (editTitle) { - modified |= XMP.setAttribute( - descriptions, - XMP.dcTitle, - title, - namespace: Namespaces.dc, - strat: XmpEditStrategy.always, - ); - } - if (editDescription) { - modified |= XMP.setAttribute( - descriptions, - XMP.dcDescription, - description, - namespace: Namespaces.dc, - strat: XmpEditStrategy.always, - ); - } - if (modified && missingDate != null) { - editCreateDateXmp(descriptions, missingDate); - } - return modified; - }); - } - - final newFields = await metadataEditService.editMetadata(this, metadata); - if (newFields.isNotEmpty) { - dataTypes.addAll({ - EntryDataType.basic, - EntryDataType.catalog, - }); - } - - return dataTypes; - } - - // write: - // - IPTC / keywords, if IPTC exists - // - XMP / dc:subject - Future> editTags(Set tags) async { - final dataTypes = {}; - final metadata = {}; - - final missingDate = await _missingDateCheckAndExifEdit(dataTypes); - - if (canEditIptc) { - final iptc = await metadataFetchService.getIptc(this); - if (iptc != null) { - editIptcValues(iptc, IPTC.applicationRecord, IPTC.keywordsTag, tags); - metadata[MetadataType.iptc] = iptc; - } - } - - if (canEditXmp) { - metadata[MetadataType.xmp] = await _editXmp((descriptions) { - final modified = editTagsXmp(descriptions, tags); - if (modified && missingDate != null) { - editCreateDateXmp(descriptions, missingDate); - } - return modified; - }); - } - - final newFields = await metadataEditService.editMetadata(this, metadata); - if (newFields.isNotEmpty) { - dataTypes.add(EntryDataType.catalog); - } - return dataTypes; - } - - // write: - // - XMP / xmp:Rating - // update: - // - XMP / MicrosoftPhoto:Rating - // ignore (Windows tags, not part of Exif 2.32 spec): - // - Exif / Rating - // - Exif / RatingPercent - Future> editRating(int? rating) async { - final dataTypes = {}; - final metadata = {}; - - final missingDate = await _missingDateCheckAndExifEdit(dataTypes); - - if (canEditXmp) { - metadata[MetadataType.xmp] = await _editXmp((descriptions) { - final modified = editRatingXmp(descriptions, rating); - if (modified && missingDate != null) { - editCreateDateXmp(descriptions, missingDate); - } - return modified; - }); - } - - final newFields = await metadataEditService.editMetadata(this, metadata); - if (newFields.isNotEmpty) { - dataTypes.add(EntryDataType.catalog); - } - return dataTypes; - } - - // remove: - // - trailer video - // - XMP / Container:Directory - // - XMP / GCamera:MicroVideo* - // - XMP / GCamera:MotionPhoto* - Future> removeTrailerVideo() async { - final dataTypes = {}; - final metadata = {}; - - if (!canEditXmp) return dataTypes; - - final missingDate = await _missingDateCheckAndExifEdit(dataTypes); - - final newFields = await metadataEditService.removeTrailerVideo(this); - - metadata[MetadataType.xmp] = await _editXmp((descriptions) { - final modified = removeContainerXmp(descriptions); - if (modified && missingDate != null) { - editCreateDateXmp(descriptions, missingDate); - } - return modified; - }); - - newFields.addAll(await metadataEditService.editMetadata(this, metadata, autoCorrectTrailerOffset: false)); - if (newFields.isNotEmpty) { - dataTypes.add(EntryDataType.catalog); - } - return dataTypes; - } - - Future> removeMetadata(Set types) async { - final dataTypes = {}; - - final newFields = await metadataEditService.removeTypes(this, types); - if (newFields.isNotEmpty) { - dataTypes.addAll({ - EntryDataType.basic, - EntryDataType.aspectRatio, - EntryDataType.catalog, - EntryDataType.address, - }); - } - return dataTypes; - } - - static void editIptcValues(List> iptc, int record, int tag, Set values) { - iptc.removeWhere((v) => v['record'] == record && v['tag'] == tag); - iptc.add({ - 'record': record, - 'tag': tag, - 'values': values.map((v) => utf8.encode(v)).toList(), - }); - } - - @visibleForTesting - static bool editCreateDateXmp(List descriptions, DateTime? date) { - return XMP.setAttribute( - descriptions, - XMP.xmpCreateDate, - date != null ? XMP.toXmpDate(date) : null, - namespace: Namespaces.xmp, - strat: XmpEditStrategy.always, - ); - } - - @visibleForTesting - static bool editTagsXmp(List descriptions, Set tags) { - return XMP.setStringBag( - descriptions, - XMP.dcSubject, - tags, - namespace: Namespaces.dc, - strat: XmpEditStrategy.always, - ); - } - - @visibleForTesting - static bool editRatingXmp(List descriptions, int? rating) { - bool modified = false; - - modified |= XMP.setAttribute( - descriptions, - XMP.xmpRating, - (rating ?? 0) == 0 ? null : '$rating', - namespace: Namespaces.xmp, - strat: XmpEditStrategy.always, - ); - - modified |= XMP.setAttribute( - descriptions, - XMP.msPhotoRating, - XMP.toMsPhotoRating(rating), - namespace: Namespaces.microsoftPhoto, - strat: XmpEditStrategy.updateIfPresent, - ); - - return modified; - } - - @visibleForTesting - static bool removeContainerXmp(List descriptions) { - bool modified = false; - - modified |= XMP.removeElements( - descriptions, - XMP.containerDirectory, - Namespaces.gContainer, - ); - - modified |= [ - XMP.gCameraMicroVideo, - XMP.gCameraMicroVideoVersion, - XMP.gCameraMicroVideoOffset, - XMP.gCameraMicroVideoPresentationTimestampUs, - XMP.gCameraMotionPhoto, - XMP.gCameraMotionPhotoVersion, - XMP.gCameraMotionPhotoPresentationTimestampUs, - ].fold(modified, (prev, name) { - return prev |= XMP.removeElements( - descriptions, - name, - Namespaces.gCamera, - ); - }); - - return modified; - } - - // convenience methods - - // This method checks whether the item already has a metadata date, - // and adds a date (the file modified date) via Exif if possible. - // It returns a date if the caller needs to add it via other metadata types (e.g. XMP). - Future _missingDateCheckAndExifEdit(Set dataTypes) async { - if (path == null) return null; - - // make sure entry is catalogued before we check whether is has a metadata date - if (!isCatalogued) { - await catalog(background: false, force: false, persist: true); - } - final dateMillis = catalogMetadata?.dateMillis; - if (dateMillis != null && dateMillis > 0) return null; - - late DateTime date; - try { - date = await File(path!).lastModified(); - } on FileSystemException catch (_) { - return null; - } - - if (canEditExif) { - final newFields = await metadataEditService.editExifDate(this, DateModifier.setCustom(const {MetadataField.exifDateOriginal}, date)); - if (newFields.isNotEmpty) { - dataTypes.addAll({ - EntryDataType.basic, - EntryDataType.catalog, - }); - return null; - } - } - - return date; - } - - Future _applyDateModifierToEntry(DateModifier modifier) async { - Set mainMetadataDate() => {canEditExif ? MetadataField.exifDateOriginal : MetadataField.xmpXmpCreateDate}; - - switch (modifier.action) { - case DateEditAction.copyField: - DateTime? date; - final source = modifier.copyFieldSource; - if (source != null) { - switch (source) { - case DateFieldSource.fileModifiedDate: - try { - if (path != null) { - final file = File(path!); - if (await file.exists()) { - date = await file.lastModified(); - } - } - } on FileSystemException catch (_) {} - break; - default: - date = await metadataFetchService.getDate(this, source.toMetadataField()!); - break; - } - } - return date != null ? DateModifier.setCustom(mainMetadataDate(), date) : null; - case DateEditAction.extractFromTitle: - final date = parseUnknownDateFormat(bestTitle); - return date != null ? DateModifier.setCustom(mainMetadataDate(), date) : null; - case DateEditAction.setCustom: - case DateEditAction.copyItem: - return DateModifier.setCustom(mainMetadataDate(), modifier.setDateTime!); - case DateEditAction.shift: - case DateEditAction.remove: - return modifier; - } - } - - static const xmpCoreKey = 'xmp'; - static const xmpExtendedKey = 'extendedXmp'; - - Future> _editXmp(bool Function(List descriptions) apply) async { - final xmp = await metadataFetchService.getXmp(this); - if (xmp == null) { - throw Exception('failed to get XMP'); - } - - final xmpString = xmp.xmpString; - final extendedXmpString = xmp.extendedXmpString; - - final editedXmpString = await XMP.edit( - xmpString, - () => PackageInfo.fromPlatform().then((v) => 'Aves v${v.version}'), - apply, - ); - - final editedXmp = AvesXmp(xmpString: editedXmpString, extendedXmpString: extendedXmpString); - return { - xmpCoreKey: editedXmp.xmpString, - xmpExtendedKey: editedXmp.extendedXmpString, - }; - } -} - -enum DescriptionField { title, description } diff --git a/lib/model/entry_extensions/multipage.dart b/lib/model/entry_extensions/multipage.dart deleted file mode 100644 index e69de29bb..000000000 From dc936ee7c559632fab80d2d7c684e3d82fec1ef0 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Tue, 14 Mar 2023 20:36:29 +0100 Subject: [PATCH 04/11] static analysis --- lib/model/filters/aspect_ratio.dart | 1 - lib/model/filters/query.dart | 1 - lib/model/settings/enums/widget_shape.dart | 1 - lib/widgets/collection/grid/section_layout.dart | 1 - lib/widgets/common/thumbnail/decorated.dart | 1 - lib/widgets/dialogs/video_stream_selection_dialog.dart | 1 - lib/widgets/filter_grids/common/filter_grid_page.dart | 1 - lib/widgets/viewer/entry_vertical_pager.dart | 2 +- lib/widgets/viewer/video/fijkplayer.dart | 1 - lib/widgets/viewer/visual/conductor.dart | 1 - lib/widgets/viewer/visual/vector.dart | 1 - 11 files changed, 1 insertion(+), 11 deletions(-) diff --git a/lib/model/filters/aspect_ratio.dart b/lib/model/filters/aspect_ratio.dart index a9a86c90b..92ee97279 100644 --- a/lib/model/filters/aspect_ratio.dart +++ b/lib/model/filters/aspect_ratio.dart @@ -1,4 +1,3 @@ -import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/query.dart'; import 'package:aves/theme/icons.dart'; diff --git a/lib/model/filters/query.dart b/lib/model/filters/query.dart index bf865cb07..9df1f55f6 100644 --- a/lib/model/filters/query.dart +++ b/lib/model/filters/query.dart @@ -1,5 +1,4 @@ import 'package:aves/model/entry/entry.dart'; -import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/theme/colors.dart'; import 'package:aves/theme/icons.dart'; diff --git a/lib/model/settings/enums/widget_shape.dart b/lib/model/settings/enums/widget_shape.dart index 7bac91601..c56a0debd 100644 --- a/lib/model/settings/enums/widget_shape.dart +++ b/lib/model/settings/enums/widget_shape.dart @@ -1,5 +1,4 @@ import 'package:aves/model/entry/entry.dart'; -import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/settings/enums/enums.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/collection/grid/section_layout.dart b/lib/widgets/collection/grid/section_layout.dart index 09a8524f4..1156b6123 100644 --- a/lib/widgets/collection/grid/section_layout.dart +++ b/lib/widgets/collection/grid/section_layout.dart @@ -1,5 +1,4 @@ import 'package:aves/model/entry/entry.dart'; -import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/section_keys.dart'; import 'package:aves/widgets/collection/grid/headers/any.dart'; diff --git a/lib/widgets/common/thumbnail/decorated.dart b/lib/widgets/common/thumbnail/decorated.dart index 9240b1f12..b4176b264 100644 --- a/lib/widgets/common/thumbnail/decorated.dart +++ b/lib/widgets/common/thumbnail/decorated.dart @@ -1,5 +1,4 @@ import 'package:aves/model/entry/entry.dart'; -import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/widgets/common/fx/borders.dart'; import 'package:aves/widgets/common/grid/overlay.dart'; import 'package:aves/widgets/common/thumbnail/image.dart'; diff --git a/lib/widgets/dialogs/video_stream_selection_dialog.dart b/lib/widgets/dialogs/video_stream_selection_dialog.dart index 59787370f..e5154ddf4 100644 --- a/lib/widgets/dialogs/video_stream_selection_dialog.dart +++ b/lib/widgets/dialogs/video_stream_selection_dialog.dart @@ -1,4 +1,3 @@ -import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/ref/languages.dart'; import 'package:aves/theme/icons.dart'; diff --git a/lib/widgets/filter_grids/common/filter_grid_page.dart b/lib/widgets/filter_grids/common/filter_grid_page.dart index 35c249d8a..26d2c0b65 100644 --- a/lib/widgets/filter_grids/common/filter_grid_page.dart +++ b/lib/widgets/filter_grids/common/filter_grid_page.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'package:aves/app_mode.dart'; -import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/highlight.dart'; import 'package:aves/model/query.dart'; diff --git a/lib/widgets/viewer/entry_vertical_pager.dart b/lib/widgets/viewer/entry_vertical_pager.dart index 37fd1c6f5..3446ca8b4 100644 --- a/lib/widgets/viewer/entry_vertical_pager.dart +++ b/lib/widgets/viewer/entry_vertical_pager.dart @@ -243,7 +243,7 @@ class _ViewerVerticalPageViewState extends State { ShowInfoIntent: CallbackAction(onInvoke: (intent) => ShowInfoPageNotification().dispatch(context)), TvShowLessInfoIntent: CallbackAction(onInvoke: (intent) => TvShowLessInfoNotification().dispatch(context)), TvShowMoreInfoIntent: CallbackAction(onInvoke: (intent) => TvShowMoreInfoNotification().dispatch(context)), - PlayPauseIntent: CallbackAction(onInvoke: (intent) => _onPlayPauseIntent(intent)), + PlayPauseIntent: CallbackAction(onInvoke: _onPlayPauseIntent), EntryActionIntent: CallbackAction(onInvoke: (intent) => _onEntryActionIntent(intent.action)), ActivateIntent: CallbackAction(onInvoke: (intent) { if (useTvLayout) { diff --git a/lib/widgets/viewer/video/fijkplayer.dart b/lib/widgets/viewer/video/fijkplayer.dart index 2237ab41b..1ee9b2a92 100644 --- a/lib/widgets/viewer/video/fijkplayer.dart +++ b/lib/widgets/viewer/video/fijkplayer.dart @@ -1,6 +1,5 @@ import 'dart:async'; -import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/settings/enums/video_loop_mode.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/video/keys.dart'; diff --git a/lib/widgets/viewer/visual/conductor.dart b/lib/widgets/viewer/visual/conductor.dart index c6801c6a1..9f07652e2 100644 --- a/lib/widgets/viewer/visual/conductor.dart +++ b/lib/widgets/viewer/visual/conductor.dart @@ -1,5 +1,4 @@ import 'package:aves/model/entry/entry.dart'; -import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/widgets/viewer/visual/state.dart'; import 'package:aves_magnifier/aves_magnifier.dart'; import 'package:collection/collection.dart'; diff --git a/lib/widgets/viewer/visual/vector.dart b/lib/widgets/viewer/visual/vector.dart index b7eb3809c..5d583d31d 100644 --- a/lib/widgets/viewer/visual/vector.dart +++ b/lib/widgets/viewer/visual/vector.dart @@ -4,7 +4,6 @@ import 'dart:ui'; import 'package:aves/image_providers/region_provider.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/extensions/images.dart'; -import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/settings/enums/entry_background.dart'; import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/settings.dart'; From 4a3b0e4a4e4f175117a2fe6997a8b1aa4adc45de Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Wed, 15 Mar 2023 00:17:51 +0100 Subject: [PATCH 05/11] refactor --- lib/model/entry/entry.dart | 2 +- lib/model/entry/extensions/catalog.dart | 1 - lib/model/entry/extensions/info.dart | 21 +- .../entry/extensions/metadata_edition.dart | 1 - lib/model/query.dart | 2 +- lib/model/settings/settings.dart | 2 +- lib/model/source/collection_lens.dart | 2 +- lib/model/video/metadata.dart | 14 +- lib/services/media/media_session_service.dart | 2 +- lib/widgets/aves_app.dart | 2 +- lib/widgets/common/map/geo_map.dart | 2 +- .../video_stream_selection_dialog.dart | 40 ++-- .../common/quick_actions/editor_page.dart | 2 +- .../viewer/action/video_action_delegate.dart | 6 +- lib/widgets/viewer/entry_viewer_stack.dart | 2 +- lib/widgets/viewer/video/fijkplayer.dart | 50 +++-- .../viewer/video/flutter_vlc_player.dart | 2 +- lib/widgets/viewer/video/video_player.dart | 2 +- plugins/aves_model/analysis_options.yaml | 5 +- plugins/aves_model/lib/aves_model.dart | 4 +- .../src/{entry_base.dart => entry/base.dart} | 0 .../aves_model/lib/src}/video/keys.dart | 0 .../lib/src/video/stream_types.dart | 9 + plugins/aves_utils/.gitignore | 30 +++ plugins/aves_utils/.metadata | 10 + plugins/aves_utils/analysis_options.yaml | 1 + plugins/aves_utils/lib/aves_utils.dart | 4 + .../aves_utils/lib/src}/change_notifier.dart | 0 .../lib/src}/optional_event_channel.dart | 0 plugins/aves_utils/pubspec.lock | 181 ++++++++++++++++++ plugins/aves_utils/pubspec.yaml | 15 ++ plugins/aves_video/lib/aves_video.dart | 1 + plugins/aves_video/lib/src/controller.dart | 28 +-- plugins/aves_video/lib/src/stream.dart | 22 +++ pubspec.lock | 7 + pubspec.yaml | 2 + 36 files changed, 360 insertions(+), 114 deletions(-) rename plugins/aves_model/lib/src/{entry_base.dart => entry/base.dart} (100%) rename {lib/model => plugins/aves_model/lib/src}/video/keys.dart (100%) create mode 100644 plugins/aves_model/lib/src/video/stream_types.dart create mode 100644 plugins/aves_utils/.gitignore create mode 100644 plugins/aves_utils/.metadata create mode 100644 plugins/aves_utils/analysis_options.yaml create mode 100644 plugins/aves_utils/lib/aves_utils.dart rename {lib/utils => plugins/aves_utils/lib/src}/change_notifier.dart (100%) rename {lib/services/common => plugins/aves_utils/lib/src}/optional_event_channel.dart (100%) create mode 100644 plugins/aves_utils/pubspec.lock create mode 100644 plugins/aves_utils/pubspec.yaml create mode 100644 plugins/aves_video/lib/src/stream.dart diff --git a/lib/model/entry/entry.dart b/lib/model/entry/entry.dart index cfa81b453..268357e2a 100644 --- a/lib/model/entry/entry.dart +++ b/lib/model/entry/entry.dart @@ -11,7 +11,7 @@ import 'package:aves/model/source/trash.dart'; import 'package:aves/ref/mime_types.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/theme/format.dart'; -import 'package:aves/utils/change_notifier.dart'; +import 'package:aves_utils/aves_utils.dart'; import 'package:aves/utils/time_utils.dart'; import 'package:aves_model/aves_model.dart'; import 'package:collection/collection.dart'; diff --git a/lib/model/entry/extensions/catalog.dart b/lib/model/entry/extensions/catalog.dart index 897f017a8..6a7aa331e 100644 --- a/lib/model/entry/extensions/catalog.dart +++ b/lib/model/entry/extensions/catalog.dart @@ -6,7 +6,6 @@ import 'package:aves/model/video/metadata.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/services/metadata/svg_metadata_service.dart'; -// TODO TLAD [split] need props extension ExtraAvesEntryCatalog on AvesEntry { Future catalog({required bool background, required bool force, required bool persist}) async { if (isCatalogued && !force) return; diff --git a/lib/model/entry/extensions/info.dart b/lib/model/entry/extensions/info.dart index 4087d10ac..46649a95f 100644 --- a/lib/model/entry/extensions/info.dart +++ b/lib/model/entry/extensions/info.dart @@ -4,7 +4,6 @@ import 'dart:collection'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/extensions/multipage.dart'; import 'package:aves/model/entry/extensions/props.dart'; -import 'package:aves/model/video/keys.dart'; import 'package:aves/model/video/metadata.dart'; import 'package:aves/ref/mime_types.dart'; import 'package:aves/services/common/services.dart'; @@ -12,11 +11,11 @@ import 'package:aves/services/metadata/svg_metadata_service.dart'; import 'package:aves/theme/colors.dart'; import 'package:aves/utils/constants.dart'; import 'package:aves/widgets/viewer/info/metadata/metadata_dir.dart'; +import 'package:aves_model/aves_model.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -// TODO TLAD [split] need props/multipage extension ExtraAvesEntryInfo on AvesEntry { // directory names may contain the name of their parent directory (as prefix + '/') // directory names may contain an index (as suffix in '[]') @@ -82,27 +81,27 @@ extension ExtraAvesEntryInfo on AvesEntry { if (mediaInfo.containsKey(Keys.streams)) { String getTypeText(Map stream) { - final type = stream[Keys.streamType] ?? StreamTypes.unknown; + final type = stream[Keys.streamType] ?? MediaStreamTypes.unknown; switch (type) { - case StreamTypes.attachment: + case MediaStreamTypes.attachment: return 'Attachment'; - case StreamTypes.audio: + case MediaStreamTypes.audio: return 'Audio'; - case StreamTypes.metadata: + case MediaStreamTypes.metadata: return 'Metadata'; - case StreamTypes.subtitle: - case StreamTypes.timedText: + case MediaStreamTypes.subtitle: + case MediaStreamTypes.timedText: return 'Text'; - case StreamTypes.video: + case MediaStreamTypes.video: return stream.containsKey(Keys.fpsDen) ? 'Video' : 'Image'; - case StreamTypes.unknown: + case MediaStreamTypes.unknown: default: return 'Unknown'; } } final allStreams = (mediaInfo[Keys.streams] as List).cast(); - final attachmentStreams = allStreams.where((stream) => stream[Keys.streamType] == StreamTypes.attachment).toList(); + final attachmentStreams = allStreams.where((stream) => stream[Keys.streamType] == MediaStreamTypes.attachment).toList(); final knownStreams = allStreams.whereNot(attachmentStreams.contains); // display known streams as separate directories (e.g. video, audio, subs) diff --git a/lib/model/entry/extensions/metadata_edition.dart b/lib/model/entry/extensions/metadata_edition.dart index c283b873f..eaa156585 100644 --- a/lib/model/entry/extensions/metadata_edition.dart +++ b/lib/model/entry/extensions/metadata_edition.dart @@ -21,7 +21,6 @@ import 'package:latlong2/latlong.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:xml/xml.dart'; -// TODO TLAD [split] need props/catalog extension ExtraAvesEntryMetadataEdition on AvesEntry { Future> editDate(DateModifier userModifier) async { final dataTypes = {}; diff --git a/lib/model/query.dart b/lib/model/query.dart index df48d2206..82be471d9 100644 --- a/lib/model/query.dart +++ b/lib/model/query.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:aves/utils/change_notifier.dart'; +import 'package:aves_utils/aves_utils.dart'; import 'package:flutter/foundation.dart'; class Query extends ChangeNotifier { diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart index 0856b3c3e..ea63e38e3 100644 --- a/lib/model/settings/settings.dart +++ b/lib/model/settings/settings.dart @@ -14,7 +14,7 @@ import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/enums/map_style.dart'; import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/services/accessibility_service.dart'; -import 'package:aves/services/common/optional_event_channel.dart'; +import 'package:aves_utils/aves_utils.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/widgets/aves_app.dart'; import 'package:aves/widgets/common/search/page.dart'; diff --git a/lib/model/source/collection_lens.dart b/lib/model/source/collection_lens.dart index 194236e83..801763194 100644 --- a/lib/model/source/collection_lens.dart +++ b/lib/model/source/collection_lens.dart @@ -19,7 +19,7 @@ import 'package:aves/model/source/events.dart'; import 'package:aves/model/source/location/location.dart'; import 'package:aves/model/source/section_keys.dart'; import 'package:aves/model/source/tag.dart'; -import 'package:aves/utils/change_notifier.dart'; +import 'package:aves_utils/aves_utils.dart'; import 'package:aves/utils/collection_utils.dart'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; diff --git a/lib/model/video/metadata.dart b/lib/model/video/metadata.dart index 73c38abae..2691bb6fe 100644 --- a/lib/model/video/metadata.dart +++ b/lib/model/video/metadata.dart @@ -4,7 +4,6 @@ import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/metadata/catalog.dart'; import 'package:aves/model/video/channel_layouts.dart'; import 'package:aves/model/video/codecs.dart'; -import 'package:aves/model/video/keys.dart'; import 'package:aves/model/video/profiles/aac.dart'; import 'package:aves/model/video/profiles/h264.dart'; import 'package:aves/model/video/profiles/hevc.dart'; @@ -17,6 +16,7 @@ import 'package:aves/utils/math_utils.dart'; import 'package:aves/utils/string_utils.dart'; import 'package:aves/utils/time_utils.dart'; import 'package:aves/widgets/viewer/video/fijkplayer.dart'; +import 'package:aves_model/aves_model.dart'; import 'package:collection/collection.dart'; import 'package:fijkplayer/fijkplayer.dart'; import 'package:flutter/foundation.dart'; @@ -230,7 +230,7 @@ class VideoMetadataFormatter { } break; case Keys.codecPixelFormat: - if (streamType == StreamTypes.video) { + if (streamType == MediaStreamTypes.video) { // this is just a short name used by FFmpeg // user-friendly descriptions for related enums are defined in libavutil/pixfmt.h save('Pixel Format', (value as String).toUpperCase()); @@ -425,13 +425,3 @@ class VideoMetadataFormatter { return '${(size / divider / divider).toStringAsFixed(round)} M$unit'; } } - -class StreamTypes { - static const attachment = 'attachment'; - static const audio = 'audio'; - static const metadata = 'metadata'; - static const subtitle = 'subtitle'; - static const timedText = 'timedtext'; - static const unknown = 'unknown'; - static const video = 'video'; -} diff --git a/lib/services/media/media_session_service.dart b/lib/services/media/media_session_service.dart index ed645eadb..8280965c7 100644 --- a/lib/services/media/media_session_service.dart +++ b/lib/services/media/media_session_service.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:aves/model/entry/entry.dart'; -import 'package:aves/services/common/optional_event_channel.dart'; +import 'package:aves_utils/aves_utils.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves_video/aves_video.dart'; import 'package:equatable/equatable.dart'; diff --git a/lib/widgets/aves_app.dart b/lib/widgets/aves_app.dart index 17f89b8d4..85fd93fae 100644 --- a/lib/widgets/aves_app.dart +++ b/lib/widgets/aves_app.dart @@ -18,7 +18,7 @@ import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/media_store_source.dart'; import 'package:aves/services/accessibility_service.dart'; -import 'package:aves/services/common/optional_event_channel.dart'; +import 'package:aves_utils/aves_utils.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/theme/colors.dart'; import 'package:aves/theme/durations.dart'; diff --git a/lib/widgets/common/map/geo_map.dart b/lib/widgets/common/map/geo_map.dart index e18bda838..af2a37f98 100644 --- a/lib/widgets/common/map/geo_map.dart +++ b/lib/widgets/common/map/geo_map.dart @@ -11,7 +11,7 @@ import 'package:aves/model/settings/settings.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; -import 'package:aves/utils/change_notifier.dart'; +import 'package:aves_utils/aves_utils.dart'; import 'package:aves/utils/constants.dart'; import 'package:aves/utils/math_utils.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; diff --git a/lib/widgets/dialogs/video_stream_selection_dialog.dart b/lib/widgets/dialogs/video_stream_selection_dialog.dart index e5154ddf4..fe14da528 100644 --- a/lib/widgets/dialogs/video_stream_selection_dialog.dart +++ b/lib/widgets/dialogs/video_stream_selection_dialog.dart @@ -13,7 +13,7 @@ import 'aves_dialog.dart'; class VideoStreamSelectionDialog extends StatefulWidget { static const routeName = '/dialog/select_video_stream'; - final Map streams; + final Map streams; const VideoStreamSelectionDialog({ super.key, @@ -25,23 +25,23 @@ class VideoStreamSelectionDialog extends StatefulWidget { } class _VideoStreamSelectionDialogState extends State { - late List _videoStreams, _audioStreams, _textStreams; - StreamSummary? _currentVideo, _currentAudio, _currentText; + late List _videoStreams, _audioStreams, _textStreams; + MediaStreamSummary? _currentVideo, _currentAudio, _currentText; @override void initState() { super.initState(); - final byType = groupBy(widget.streams.keys, (v) => v.type); + final byType = groupBy(widget.streams.keys, (v) => v.type); // check width/height to exclude image streams (that are included among video streams) - _videoStreams = (byType[StreamType.video] ?? []).where((v) => v.width != null && v.height != null).toList(); - _audioStreams = (byType[StreamType.audio] ?? []); - _textStreams = [null, ...byType[StreamType.text] ?? []]; + _videoStreams = (byType[MediaStreamType.video] ?? []).where((v) => v.width != null && v.height != null).toList(); + _audioStreams = (byType[MediaStreamType.audio] ?? []); + _textStreams = [null, ...byType[MediaStreamType.text] ?? []]; final streamEntries = widget.streams.entries; - _currentVideo = streamEntries.firstWhereOrNull((kv) => kv.key.type == StreamType.video && kv.value)?.key; - _currentAudio = streamEntries.firstWhereOrNull((kv) => kv.key.type == StreamType.audio && kv.value)?.key; - _currentText = streamEntries.firstWhereOrNull((kv) => kv.key.type == StreamType.text && kv.value)?.key; + _currentVideo = streamEntries.firstWhereOrNull((kv) => kv.key.type == MediaStreamType.video && kv.value)?.key; + _currentAudio = streamEntries.firstWhereOrNull((kv) => kv.key.type == MediaStreamType.audio && kv.value)?.key; + _currentText = streamEntries.firstWhereOrNull((kv) => kv.key.type == MediaStreamType.text && kv.value)?.key; } @override @@ -97,7 +97,7 @@ class _VideoStreamSelectionDialogState extends State return language?.native ?? value; } - String _commonStreamName(StreamSummary? stream) { + String _commonStreamName(MediaStreamSummary? stream) { if (stream == null) return context.l10n.videoStreamSelectionDialogOff; final title = stream.title; final language = stream.language; @@ -111,9 +111,9 @@ class _VideoStreamSelectionDialogState extends State } } - String _streamName(StreamSummary? stream) { + String _streamName(MediaStreamSummary? stream) { final common = _commonStreamName(stream); - if (stream != null && stream.type == StreamType.video) { + if (stream != null && stream.type == MediaStreamType.video) { final w = stream.width; final h = stream.height; if (w != null && h != null) { @@ -126,9 +126,9 @@ class _VideoStreamSelectionDialogState extends State List _buildSection({ required IconData icon, required String title, - required List streams, - required StreamSummary? current, - required ValueSetter setter, + required List streams, + required MediaStreamSummary? current, + required ValueSetter setter, }) { return [ Padding( @@ -143,7 +143,7 @@ class _VideoStreamSelectionDialogState extends State ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: TextDropdownButton( + child: TextDropdownButton( values: streams.whereNotNull().toList(), valueText: _streamName, value: current, @@ -156,8 +156,8 @@ class _VideoStreamSelectionDialogState extends State } void _submit(BuildContext context) => Navigator.maybeOf(context)?.pop({ - StreamType.video: _currentVideo, - StreamType.audio: _currentAudio, - StreamType.text: _currentText, + MediaStreamType.video: _currentVideo, + MediaStreamType.audio: _currentAudio, + MediaStreamType.text: _currentText, }); } diff --git a/lib/widgets/settings/common/quick_actions/editor_page.dart b/lib/widgets/settings/common/quick_actions/editor_page.dart index 4736dd4d4..120e2e55d 100644 --- a/lib/widgets/settings/common/quick_actions/editor_page.dart +++ b/lib/widgets/settings/common/quick_actions/editor_page.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; -import 'package:aves/utils/change_notifier.dart'; +import 'package:aves_utils/aves_utils.dart'; import 'package:aves/utils/constants.dart'; import 'package:aves/widgets/common/basic/scaffold.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; diff --git a/lib/widgets/viewer/action/video_action_delegate.dart b/lib/widgets/viewer/action/video_action_delegate.dart index 81d91e871..df67b5a91 100644 --- a/lib/widgets/viewer/action/video_action_delegate.dart +++ b/lib/widgets/viewer/action/video_action_delegate.dart @@ -144,10 +144,10 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix Future _showStreamSelectionDialog(BuildContext context, AvesVideoController controller) async { final streams = controller.streams; - final currentSelectedStreams = await Future.wait(StreamType.values.map(controller.getSelectedStream)); + final currentSelectedStreams = await Future.wait(MediaStreamType.values.map(controller.getSelectedStream)); final currentSelectedIndices = currentSelectedStreams.whereNotNull().map((v) => v.index).toSet(); - final userSelectedStreams = await showDialog>( + final userSelectedStreams = await showDialog>( context: context, builder: (context) => VideoStreamSelectionDialog( streams: Map.fromEntries(streams.map((stream) => MapEntry(stream, currentSelectedIndices.contains(stream.index)))), @@ -156,7 +156,7 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix ); if (userSelectedStreams == null || userSelectedStreams.isEmpty) return; - await Future.forEach>( + await Future.forEach>( userSelectedStreams.entries, (kv) => controller.selectStream(kv.key, kv.value), ); diff --git a/lib/widgets/viewer/entry_viewer_stack.dart b/lib/widgets/viewer/entry_viewer_stack.dart index 876ef773b..900943741 100644 --- a/lib/widgets/viewer/entry_viewer_stack.dart +++ b/lib/widgets/viewer/entry_viewer_stack.dart @@ -15,7 +15,7 @@ import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/theme/durations.dart'; -import 'package:aves/utils/change_notifier.dart'; +import 'package:aves_utils/aves_utils.dart'; import 'package:aves/widgets/aves_app.dart'; import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/common/action_mixins/feedback.dart'; diff --git a/lib/widgets/viewer/video/fijkplayer.dart b/lib/widgets/viewer/video/fijkplayer.dart index 1ee9b2a92..bd373c087 100644 --- a/lib/widgets/viewer/video/fijkplayer.dart +++ b/lib/widgets/viewer/video/fijkplayer.dart @@ -2,10 +2,8 @@ import 'dart:async'; import 'package:aves/model/settings/enums/video_loop_mode.dart'; import 'package:aves/model/settings/settings.dart'; -import 'package:aves/model/video/keys.dart'; -import 'package:aves/model/video/metadata.dart'; -import 'package:aves/services/common/optional_event_channel.dart'; -import 'package:aves/utils/change_notifier.dart'; +import 'package:aves_model/aves_model.dart'; +import 'package:aves_utils/aves_utils.dart'; import 'package:aves_video/aves_video.dart'; import 'package:collection/collection.dart'; import 'package:fijkplayer/fijkplayer.dart'; @@ -23,7 +21,7 @@ class IjkPlayerAvesVideoController extends AvesVideoController { final StreamController _speedStreamController = StreamController.broadcast(); final AChangeNotifier _completedNotifier = AChangeNotifier(); Offset _macroBlockCrop = Offset.zero; - final List _streams = []; + final List _streams = []; Timer? _initialPlayTimer; double _speed = 1; double _volume = 1; @@ -257,7 +255,7 @@ class IjkPlayerAvesVideoController extends AvesVideoController { if (type != null) { final width = stream[Keys.width] as int?; final height = stream[Keys.height] as int?; - _streams.add(StreamSummary( + _streams.add(MediaStreamSummary( type: type, index: stream[Keys.index], codecName: stream[Keys.codecName], @@ -267,16 +265,16 @@ class IjkPlayerAvesVideoController extends AvesVideoController { height: height, )); switch (type) { - case StreamType.video: + case MediaStreamType.video: // check width/height to exclude image streams (that are included among video streams) if (width != null && height != null) { videoStreamCount++; } break; - case StreamType.audio: + case MediaStreamType.audio: audioStreamCount++; break; - case StreamType.text: + case MediaStreamType.text: textStreamCount++; break; } @@ -285,7 +283,7 @@ class IjkPlayerAvesVideoController extends AvesVideoController { canSelectStreamNotifier.value = videoStreamCount > 1 || audioStreamCount > 1 || textStreamCount > 0; - final selectedVideo = await getSelectedStream(StreamType.video); + final selectedVideo = await getSelectedStream(MediaStreamType.video); if (selectedVideo != null) { final streamIndex = selectedVideo.index; final streamInfo = allStreams.firstWhereOrNull((stream) => stream[Keys.index] == streamIndex); @@ -435,7 +433,7 @@ class IjkPlayerAvesVideoController extends AvesVideoController { // 1) prevent video stream acceleration to catch up with audio // 2) apply timed text stream @override - Future selectStream(StreamType type, StreamSummary? selected) async { + Future selectStream(MediaStreamType type, MediaStreamSummary? selected) async { final current = await getSelectedStream(type); if (current != selected) { if (selected != null) { @@ -446,7 +444,7 @@ class IjkPlayerAvesVideoController extends AvesVideoController { } else if (current != null) { await _instance.deselectTrack(current.index!); } - if (type == StreamType.text) { + if (type == MediaStreamType.text) { _timedTextStreamController.add(null); } await seekTo(currentPosition); @@ -454,13 +452,13 @@ class IjkPlayerAvesVideoController extends AvesVideoController { } @override - Future getSelectedStream(StreamType type) async { + Future getSelectedStream(MediaStreamType type) async { final currentIndex = await _instance.getSelectedTrack(type.code); return currentIndex != -1 ? _streams.firstWhereOrNull((v) => v.index == currentIndex) : null; } @override - List get streams => _streams; + List get streams => _streams; @override Future captureFrame() { @@ -556,16 +554,16 @@ extension ExtraFijkPlayer on FijkPlayer { } } -extension ExtraStreamType on StreamType { - static StreamType? fromTypeString(String? type) { +extension ExtraStreamType on MediaStreamType { + static MediaStreamType? fromTypeString(String? type) { switch (type) { - case StreamTypes.video: - return StreamType.video; - case StreamTypes.audio: - return StreamType.audio; - case StreamTypes.subtitle: - case StreamTypes.timedText: - return StreamType.text; + case MediaStreamTypes.video: + return MediaStreamType.video; + case MediaStreamTypes.audio: + return MediaStreamType.audio; + case MediaStreamTypes.subtitle: + case MediaStreamTypes.timedText: + return MediaStreamType.text; default: return null; } @@ -574,11 +572,11 @@ extension ExtraStreamType on StreamType { int get code { // codes from ijkplayer ITrackInfo.java switch (this) { - case StreamType.video: + case MediaStreamType.video: return 1; - case StreamType.audio: + case MediaStreamType.audio: return 2; - case StreamType.text: + case MediaStreamType.text: // TIMEDTEXT = 3, SUBTITLE = 4 return 3; default: diff --git a/lib/widgets/viewer/video/flutter_vlc_player.dart b/lib/widgets/viewer/video/flutter_vlc_player.dart index df28f0ffc..f9de65fbe 100644 --- a/lib/widgets/viewer/video/flutter_vlc_player.dart +++ b/lib/widgets/viewer/video/flutter_vlc_player.dart @@ -2,7 +2,7 @@ // import 'dart:io'; // // import 'package:aves/model/entry.dart'; -// import 'package:aves/utils/change_notifier.dart'; +// import 'package:aves_utils/aves_utils.dart'; // import 'package:aves_video/aves_video.dart'; // import 'package:flutter/material.dart'; // import 'package:flutter/src/foundation/change_notifier.dart'; diff --git a/lib/widgets/viewer/video/video_player.dart b/lib/widgets/viewer/video/video_player.dart index b3b4a663c..0aa9cf8c1 100644 --- a/lib/widgets/viewer/video/video_player.dart +++ b/lib/widgets/viewer/video/video_player.dart @@ -1,7 +1,7 @@ // import 'dart:async'; // // import 'package:aves/model/entry.dart'; -// import 'package:aves/utils/change_notifier.dart'; +// import 'package:aves_utils/aves_utils.dart'; // import 'package:aves_video/aves_video.dart'; // import 'package:flutter/src/foundation/change_notifier.dart'; // import 'package:flutter/src/widgets/framework.dart'; diff --git a/plugins/aves_model/analysis_options.yaml b/plugins/aves_model/analysis_options.yaml index a5744c1cf..f04c6cf0f 100644 --- a/plugins/aves_model/analysis_options.yaml +++ b/plugins/aves_model/analysis_options.yaml @@ -1,4 +1 @@ -include: package:flutter_lints/flutter.yaml - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options +include: ../../analysis_options.yaml diff --git a/plugins/aves_model/lib/aves_model.dart b/plugins/aves_model/lib/aves_model.dart index 4f604ce32..d892a954c 100644 --- a/plugins/aves_model/lib/aves_model.dart +++ b/plugins/aves_model/lib/aves_model.dart @@ -1,3 +1,5 @@ library aves_model; -export 'src/entry_base.dart'; +export 'src/entry/base.dart'; +export 'src/video/keys.dart'; +export 'src/video/stream_types.dart'; diff --git a/plugins/aves_model/lib/src/entry_base.dart b/plugins/aves_model/lib/src/entry/base.dart similarity index 100% rename from plugins/aves_model/lib/src/entry_base.dart rename to plugins/aves_model/lib/src/entry/base.dart diff --git a/lib/model/video/keys.dart b/plugins/aves_model/lib/src/video/keys.dart similarity index 100% rename from lib/model/video/keys.dart rename to plugins/aves_model/lib/src/video/keys.dart diff --git a/plugins/aves_model/lib/src/video/stream_types.dart b/plugins/aves_model/lib/src/video/stream_types.dart new file mode 100644 index 000000000..d2be820f3 --- /dev/null +++ b/plugins/aves_model/lib/src/video/stream_types.dart @@ -0,0 +1,9 @@ +class MediaStreamTypes { + static const attachment = 'attachment'; + static const audio = 'audio'; + static const metadata = 'metadata'; + static const subtitle = 'subtitle'; + static const timedText = 'timedtext'; + static const unknown = 'unknown'; + static const video = 'video'; +} diff --git a/plugins/aves_utils/.gitignore b/plugins/aves_utils/.gitignore new file mode 100644 index 000000000..28124a571 --- /dev/null +++ b/plugins/aves_utils/.gitignore @@ -0,0 +1,30 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +#/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/plugins/aves_utils/.metadata b/plugins/aves_utils/.metadata new file mode 100644 index 000000000..ad910f56c --- /dev/null +++ b/plugins/aves_utils/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 2ad6cd72c040113b47ee9055e722606a490ef0da + channel: stable + +project_type: package diff --git a/plugins/aves_utils/analysis_options.yaml b/plugins/aves_utils/analysis_options.yaml new file mode 100644 index 000000000..f04c6cf0f --- /dev/null +++ b/plugins/aves_utils/analysis_options.yaml @@ -0,0 +1 @@ +include: ../../analysis_options.yaml diff --git a/plugins/aves_utils/lib/aves_utils.dart b/plugins/aves_utils/lib/aves_utils.dart new file mode 100644 index 000000000..1921022e4 --- /dev/null +++ b/plugins/aves_utils/lib/aves_utils.dart @@ -0,0 +1,4 @@ +library aves_utils; + +export 'src/change_notifier.dart'; +export 'src/optional_event_channel.dart'; diff --git a/lib/utils/change_notifier.dart b/plugins/aves_utils/lib/src/change_notifier.dart similarity index 100% rename from lib/utils/change_notifier.dart rename to plugins/aves_utils/lib/src/change_notifier.dart diff --git a/lib/services/common/optional_event_channel.dart b/plugins/aves_utils/lib/src/optional_event_channel.dart similarity index 100% rename from lib/services/common/optional_event_channel.dart rename to plugins/aves_utils/lib/src/optional_event_channel.dart diff --git a/plugins/aves_utils/pubspec.lock b/plugins/aves_utils/pubspec.lock new file mode 100644 index 000000000..b61cdad6b --- /dev/null +++ b/plugins/aves_utils/pubspec.lock @@ -0,0 +1,181 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + url: "https://pub.dev" + source: hosted + version: "2.10.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + url: "https://pub.dev" + source: hosted + version: "1.2.1" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + url: "https://pub.dev" + source: hosted + version: "1.17.0" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + url: "https://pub.dev" + source: hosted + version: "2.0.1" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + js: + dependency: transitive + description: + name: js + sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + url: "https://pub.dev" + source: hosted + version: "0.6.5" + lints: + dependency: transitive + description: + name: lints + sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + url: "https://pub.dev" + source: hosted + version: "0.12.13" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + meta: + dependency: transitive + description: + name: meta + sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + url: "https://pub.dev" + source: hosted + version: "1.8.0" + path: + dependency: transitive + description: + name: path + sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + url: "https://pub.dev" + source: hosted + version: "1.8.2" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + url: "https://pub.dev" + source: hosted + version: "1.9.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" + source: hosted + version: "1.11.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + url: "https://pub.dev" + source: hosted + version: "0.4.16" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" +sdks: + dart: ">=2.19.4 <3.0.0" + flutter: ">=1.17.0" diff --git a/plugins/aves_utils/pubspec.yaml b/plugins/aves_utils/pubspec.yaml new file mode 100644 index 000000000..3828c7ab0 --- /dev/null +++ b/plugins/aves_utils/pubspec.yaml @@ -0,0 +1,15 @@ +name: aves_utils +version: 0.0.1 +publish_to: none + +environment: + sdk: '>=2.19.4 <3.0.0' + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_lints: + +flutter: diff --git a/plugins/aves_video/lib/aves_video.dart b/plugins/aves_video/lib/aves_video.dart index 90e236285..6c308814d 100644 --- a/plugins/aves_video/lib/aves_video.dart +++ b/plugins/aves_video/lib/aves_video.dart @@ -1,3 +1,4 @@ library aves_video; export 'src/controller.dart'; +export 'src/stream.dart'; diff --git a/plugins/aves_video/lib/src/controller.dart b/plugins/aves_video/lib/src/controller.dart index 94330e789..3d658ecb8 100644 --- a/plugins/aves_video/lib/src/controller.dart +++ b/plugins/aves_video/lib/src/controller.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:aves_model/aves_model.dart'; +import 'package:aves_video/src/stream.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; @@ -86,11 +87,11 @@ abstract class AvesVideoController { set speed(double speed); - Future selectStream(StreamType type, StreamSummary? selected); + Future selectStream(MediaStreamType type, MediaStreamSummary? selected); - Future getSelectedStream(StreamType type); + Future getSelectedStream(MediaStreamType type); - List get streams; + List get streams; Future captureFrame(); @@ -108,27 +109,6 @@ enum VideoStatus { error, } -enum StreamType { video, audio, text } - -class StreamSummary { - final StreamType type; - final int? index, width, height; - final String? codecName, language, title; - - const StreamSummary({ - required this.type, - required this.index, - required this.codecName, - required this.language, - required this.title, - required this.width, - required this.height, - }); - - @override - String toString() => '$runtimeType#${shortHash(this)}{type: type, index: $index, codecName: $codecName, language: $language, title: $title, width: $width, height: $height}'; -} - abstract class PlaybackStateHandler { Future getResumeTime({required int entryId, required BuildContext context}); diff --git a/plugins/aves_video/lib/src/stream.dart b/plugins/aves_video/lib/src/stream.dart new file mode 100644 index 000000000..93c4bb05b --- /dev/null +++ b/plugins/aves_video/lib/src/stream.dart @@ -0,0 +1,22 @@ +import 'package:flutter/foundation.dart'; + +enum MediaStreamType { video, audio, text } + +class MediaStreamSummary { + final MediaStreamType type; + final int? index, width, height; + final String? codecName, language, title; + + const MediaStreamSummary({ + required this.type, + required this.index, + required this.codecName, + required this.language, + required this.title, + required this.width, + required this.height, + }); + + @override + String toString() => '$runtimeType#${shortHash(this)}{type: type, index: $index, codecName: $codecName, language: $language, title: $title, width: $width, height: $height}'; +} diff --git a/pubspec.lock b/pubspec.lock index c894bb047..26c878c58 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -105,6 +105,13 @@ packages: relative: true source: path version: "0.0.1" + aves_utils: + dependency: "direct main" + description: + path: "plugins/aves_utils" + relative: true + source: path + version: "0.0.1" aves_video: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 74b0dc351..5357ab73f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -43,6 +43,8 @@ dependencies: path: plugins/aves_video aves_ui: path: plugins/aves_ui + aves_utils: + path: plugins/aves_utils charts_flutter: git: url: https://github.com/fzyzcjy/charts.git From cb5afa9014ecbdbbfcd8c36698d7c4cb5c9a830c Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Wed, 15 Mar 2023 00:47:14 +0100 Subject: [PATCH 06/11] refactor --- .../enums/accessibility_animations.dart | 16 +- .../settings/enums/accessibility_timeout.dart | 23 -- .../settings/enums/coordinate_format.dart | 14 +- .../enums/display_refresh_rate_mode.dart | 17 +- .../settings/enums/entry_background.dart | 3 +- lib/model/settings/enums/home_page.dart | 14 +- lib/model/settings/enums/l10n.dart | 278 ++++++++++++++++++ lib/model/settings/enums/map_style.dart | 23 -- lib/model/settings/enums/screen_on.dart | 18 +- .../enums/slideshow_video_playback.dart | 17 -- .../settings/enums/subtitle_position.dart | 13 +- .../settings/enums/theme_brightness.dart | 17 +- .../thumbnail_overlay_location_icon.dart | 17 +- .../enums/thumbnail_overlay_tag_icon.dart | 17 +- lib/model/settings/enums/unit_system.dart | 15 - .../settings/enums/video_auto_play_mode.dart | 17 -- .../settings/enums/video_background_mode.dart | 15 - lib/model/settings/enums/video_controls.dart | 19 -- lib/model/settings/enums/video_loop_mode.dart | 16 +- .../settings/enums/viewer_transition.dart | 19 +- .../settings/enums/widget_displayed_item.dart | 14 - .../settings/enums/widget_open_action.dart | 16 - lib/model/settings/enums/widget_shape.dart | 2 +- lib/model/source/collection_lens.dart | 5 +- lib/model/source/enums/view.dart | 3 +- lib/widgets/common/map/geo_map.dart | 5 +- .../common/map/map_action_delegate.dart | 2 +- .../settings/accessibility/accessibility.dart | 2 +- .../accessibility/time_to_take_action.dart | 2 +- lib/widgets/settings/display/display.dart | 3 +- .../settings/home_widget_settings_page.dart | 3 +- lib/widgets/settings/language/language.dart | 2 +- .../settings/navigation/navigation.dart | 3 +- .../settings/screen_saver_settings_page.dart | 3 +- lib/widgets/settings/thumbnails/overlay.dart | 1 + lib/widgets/settings/video/controls.dart | 2 +- .../settings/video/subtitle_theme.dart | 2 +- lib/widgets/settings/video/video.dart | 4 +- lib/widgets/settings/viewer/slideshow.dart | 3 +- 39 files changed, 313 insertions(+), 352 deletions(-) delete mode 100644 lib/model/settings/enums/accessibility_timeout.dart create mode 100644 lib/model/settings/enums/l10n.dart delete mode 100644 lib/model/settings/enums/slideshow_video_playback.dart delete mode 100644 lib/model/settings/enums/unit_system.dart delete mode 100644 lib/model/settings/enums/video_auto_play_mode.dart delete mode 100644 lib/model/settings/enums/video_background_mode.dart delete mode 100644 lib/model/settings/enums/video_controls.dart delete mode 100644 lib/model/settings/enums/widget_displayed_item.dart delete mode 100644 lib/model/settings/enums/widget_open_action.dart diff --git a/lib/model/settings/enums/accessibility_animations.dart b/lib/model/settings/enums/accessibility_animations.dart index d301f098e..e9b6b1184 100644 --- a/lib/model/settings/enums/accessibility_animations.dart +++ b/lib/model/settings/enums/accessibility_animations.dart @@ -1,21 +1,7 @@ +import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/settings.dart'; -import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:flutter/widgets.dart'; - -import 'enums.dart'; extension ExtraAccessibilityAnimations on AccessibilityAnimations { - String getName(BuildContext context) { - switch (this) { - case AccessibilityAnimations.system: - return context.l10n.settingsSystemDefault; - case AccessibilityAnimations.disabled: - return context.l10n.accessibilityAnimationsRemove; - case AccessibilityAnimations.enabled: - return context.l10n.accessibilityAnimationsKeep; - } - } - bool get animate { switch (this) { case AccessibilityAnimations.system: diff --git a/lib/model/settings/enums/accessibility_timeout.dart b/lib/model/settings/enums/accessibility_timeout.dart deleted file mode 100644 index 5c7529f77..000000000 --- a/lib/model/settings/enums/accessibility_timeout.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:flutter/widgets.dart'; - -import 'enums.dart'; - -extension ExtraAccessibilityTimeout on AccessibilityTimeout { - String getName(BuildContext context) { - switch (this) { - case AccessibilityTimeout.system: - return context.l10n.settingsSystemDefault; - case AccessibilityTimeout.s1: - return context.l10n.timeSeconds(1); - case AccessibilityTimeout.s3: - return context.l10n.timeSeconds(3); - case AccessibilityTimeout.s5: - return context.l10n.timeSeconds(5); - case AccessibilityTimeout.s10: - return context.l10n.timeSeconds(10); - case AccessibilityTimeout.s30: - return context.l10n.timeSeconds(30); - } - } -} diff --git a/lib/model/settings/enums/coordinate_format.dart b/lib/model/settings/enums/coordinate_format.dart index 4b3b2ffd9..c03fe3727 100644 --- a/lib/model/settings/enums/coordinate_format.dart +++ b/lib/model/settings/enums/coordinate_format.dart @@ -1,21 +1,9 @@ import 'package:aves/l10n/l10n.dart'; -import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:flutter/widgets.dart'; +import 'package:aves/model/settings/enums/enums.dart'; import 'package:intl/intl.dart'; import 'package:latlong2/latlong.dart'; -import 'enums.dart'; - extension ExtraCoordinateFormat on CoordinateFormat { - String getName(BuildContext context) { - switch (this) { - case CoordinateFormat.dms: - return context.l10n.coordinateFormatDms; - case CoordinateFormat.decimal: - return context.l10n.coordinateFormatDecimal; - } - } - static const _separator = ', '; String format(AppLocalizations l10n, LatLng latLng, {bool minuteSecondPadding = false, int dmsSecondDecimals = 2}) { diff --git a/lib/model/settings/enums/display_refresh_rate_mode.dart b/lib/model/settings/enums/display_refresh_rate_mode.dart index 37c1d76f2..79bf93508 100644 --- a/lib/model/settings/enums/display_refresh_rate_mode.dart +++ b/lib/model/settings/enums/display_refresh_rate_mode.dart @@ -1,23 +1,10 @@ +import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/services/common/services.dart'; -import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:device_info_plus/device_info_plus.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart'; -import 'enums.dart'; - extension ExtraDisplayRefreshRateMode on DisplayRefreshRateMode { - String getName(BuildContext context) { - switch (this) { - case DisplayRefreshRateMode.auto: - return context.l10n.settingsSystemDefault; - case DisplayRefreshRateMode.highest: - return context.l10n.displayRefreshRatePreferHighest; - case DisplayRefreshRateMode.lowest: - return context.l10n.displayRefreshRatePreferLowest; - } - } - Future apply() async { if (!await windowService.isActivity()) return; diff --git a/lib/model/settings/enums/entry_background.dart b/lib/model/settings/enums/entry_background.dart index c9935b6d1..b4fc3a636 100644 --- a/lib/model/settings/enums/entry_background.dart +++ b/lib/model/settings/enums/entry_background.dart @@ -1,7 +1,6 @@ +import 'package:aves/model/settings/enums/enums.dart'; import 'package:flutter/material.dart'; -import 'enums.dart'; - extension ExtraEntryBackground on EntryBackground { bool get isColor { switch (this) { diff --git a/lib/model/settings/enums/home_page.dart b/lib/model/settings/enums/home_page.dart index c52228d8a..4cdf8b057 100644 --- a/lib/model/settings/enums/home_page.dart +++ b/lib/model/settings/enums/home_page.dart @@ -1,20 +1,8 @@ +import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/widgets/collection/collection_page.dart'; -import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart'; -import 'package:flutter/widgets.dart'; - -import 'enums.dart'; extension ExtraHomePageSetting on HomePageSetting { - String getName(BuildContext context) { - switch (this) { - case HomePageSetting.collection: - return context.l10n.drawerCollectionAll; - case HomePageSetting.albums: - return context.l10n.drawerAlbumPage; - } - } - String get routeName { switch (this) { case HomePageSetting.collection: diff --git a/lib/model/settings/enums/l10n.dart b/lib/model/settings/enums/l10n.dart new file mode 100644 index 000000000..88ce874bf --- /dev/null +++ b/lib/model/settings/enums/l10n.dart @@ -0,0 +1,278 @@ +import 'package:aves/model/settings/enums/enums.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves_map/aves_map.dart'; +import 'package:flutter/widgets.dart'; + +extension ExtraAccessibilityAnimationsName on AccessibilityAnimations { + String getName(BuildContext context) { + switch (this) { + case AccessibilityAnimations.system: + return context.l10n.settingsSystemDefault; + case AccessibilityAnimations.disabled: + return context.l10n.accessibilityAnimationsRemove; + case AccessibilityAnimations.enabled: + return context.l10n.accessibilityAnimationsKeep; + } + } +} + +extension ExtraAccessibilityTimeoutName on AccessibilityTimeout { + String getName(BuildContext context) { + switch (this) { + case AccessibilityTimeout.system: + return context.l10n.settingsSystemDefault; + case AccessibilityTimeout.s1: + return context.l10n.timeSeconds(1); + case AccessibilityTimeout.s3: + return context.l10n.timeSeconds(3); + case AccessibilityTimeout.s5: + return context.l10n.timeSeconds(5); + case AccessibilityTimeout.s10: + return context.l10n.timeSeconds(10); + case AccessibilityTimeout.s30: + return context.l10n.timeSeconds(30); + } + } +} + +extension ExtraAvesThemeBrightnessName on AvesThemeBrightness { + String getName(BuildContext context) { + switch (this) { + case AvesThemeBrightness.system: + return context.l10n.settingsSystemDefault; + case AvesThemeBrightness.light: + return context.l10n.themeBrightnessLight; + case AvesThemeBrightness.dark: + return context.l10n.themeBrightnessDark; + case AvesThemeBrightness.black: + return context.l10n.themeBrightnessBlack; + } + } +} + +extension ExtraCoordinateFormatName on CoordinateFormat { + String getName(BuildContext context) { + switch (this) { + case CoordinateFormat.dms: + return context.l10n.coordinateFormatDms; + case CoordinateFormat.decimal: + return context.l10n.coordinateFormatDecimal; + } + } +} + +extension ExtraDisplayRefreshRateModeName on DisplayRefreshRateMode { + String getName(BuildContext context) { + switch (this) { + case DisplayRefreshRateMode.auto: + return context.l10n.settingsSystemDefault; + case DisplayRefreshRateMode.highest: + return context.l10n.displayRefreshRatePreferHighest; + case DisplayRefreshRateMode.lowest: + return context.l10n.displayRefreshRatePreferLowest; + } + } +} + +extension ExtraEntryMapStyleName on EntryMapStyle { + String getName(BuildContext context) { + switch (this) { + case EntryMapStyle.googleNormal: + return context.l10n.mapStyleGoogleNormal; + case EntryMapStyle.googleHybrid: + return context.l10n.mapStyleGoogleHybrid; + case EntryMapStyle.googleTerrain: + return context.l10n.mapStyleGoogleTerrain; + case EntryMapStyle.hmsNormal: + return context.l10n.mapStyleHuaweiNormal; + case EntryMapStyle.hmsTerrain: + return context.l10n.mapStyleHuaweiTerrain; + case EntryMapStyle.osmHot: + return context.l10n.mapStyleOsmHot; + case EntryMapStyle.stamenToner: + return context.l10n.mapStyleStamenToner; + case EntryMapStyle.stamenWatercolor: + return context.l10n.mapStyleStamenWatercolor; + } + } +} + +extension ExtraHomePageSettingName on HomePageSetting { + String getName(BuildContext context) { + switch (this) { + case HomePageSetting.collection: + return context.l10n.drawerCollectionAll; + case HomePageSetting.albums: + return context.l10n.drawerAlbumPage; + } + } +} + +extension ExtraKeepScreenOnName on KeepScreenOn { + String getName(BuildContext context) { + switch (this) { + case KeepScreenOn.never: + return context.l10n.keepScreenOnNever; + case KeepScreenOn.videoPlayback: + return context.l10n.keepScreenOnVideoPlayback; + case KeepScreenOn.viewerOnly: + return context.l10n.keepScreenOnViewerOnly; + case KeepScreenOn.always: + return context.l10n.keepScreenOnAlways; + } + } +} + +extension ExtraSlideshowVideoPlaybackName on SlideshowVideoPlayback { + String getName(BuildContext context) { + switch (this) { + case SlideshowVideoPlayback.skip: + return context.l10n.videoPlaybackSkip; + case SlideshowVideoPlayback.playMuted: + return context.l10n.videoPlaybackMuted; + case SlideshowVideoPlayback.playWithSound: + return context.l10n.videoPlaybackWithSound; + } + } +} + +extension ExtraSubtitlePositionName on SubtitlePosition { + String getName(BuildContext context) { + switch (this) { + case SubtitlePosition.top: + return context.l10n.subtitlePositionTop; + case SubtitlePosition.bottom: + return context.l10n.subtitlePositionBottom; + } + } +} + +extension ExtraThumbnailOverlayLocationIconName on ThumbnailOverlayLocationIcon { + String getName(BuildContext context) { + switch (this) { + case ThumbnailOverlayLocationIcon.located: + return context.l10n.filterLocatedLabel; + case ThumbnailOverlayLocationIcon.unlocated: + return context.l10n.filterNoLocationLabel; + case ThumbnailOverlayLocationIcon.none: + return context.l10n.settingsDisabled; + } + } +} + +extension ExtraThumbnailOverlayTagIconName on ThumbnailOverlayTagIcon { + String getName(BuildContext context) { + switch (this) { + case ThumbnailOverlayTagIcon.tagged: + return context.l10n.filterTaggedLabel; + case ThumbnailOverlayTagIcon.untagged: + return context.l10n.filterNoTagLabel; + case ThumbnailOverlayTagIcon.none: + return context.l10n.settingsDisabled; + } + } +} + +extension ExtraUnitSystemName on UnitSystem { + String getName(BuildContext context) { + switch (this) { + case UnitSystem.metric: + return context.l10n.unitSystemMetric; + case UnitSystem.imperial: + return context.l10n.unitSystemImperial; + } + } +} + +extension ExtraVideoAutoPlayModeName on VideoAutoPlayMode { + String getName(BuildContext context) { + switch (this) { + case VideoAutoPlayMode.disabled: + return context.l10n.settingsDisabled; + case VideoAutoPlayMode.playMuted: + return context.l10n.videoPlaybackMuted; + case VideoAutoPlayMode.playWithSound: + return context.l10n.videoPlaybackWithSound; + } + } +} + +extension ExtraVideoBackgroundModeName on VideoBackgroundMode { + String getName(BuildContext context) { + switch (this) { + case VideoBackgroundMode.disabled: + return context.l10n.settingsDisabled; + case VideoBackgroundMode.pip: + return context.l10n.settingsVideoEnablePip; + } + } +} + +extension ExtraVideoControlsName on VideoControls { + String getName(BuildContext context) { + switch (this) { + case VideoControls.play: + return context.l10n.videoControlsPlay; + case VideoControls.playSeek: + return context.l10n.videoControlsPlaySeek; + case VideoControls.playOutside: + return context.l10n.videoControlsPlayOutside; + case VideoControls.none: + return context.l10n.videoControlsNone; + } + } +} + +extension ExtraVideoLoopModeName on VideoLoopMode { + String getName(BuildContext context) { + switch (this) { + case VideoLoopMode.never: + return context.l10n.videoLoopModeNever; + case VideoLoopMode.shortOnly: + return context.l10n.videoLoopModeShortOnly; + case VideoLoopMode.always: + return context.l10n.videoLoopModeAlways; + } + } +} + +extension ExtraViewerTransitionName on ViewerTransition { + String getName(BuildContext context) { + switch (this) { + case ViewerTransition.slide: + return context.l10n.viewerTransitionSlide; + case ViewerTransition.parallax: + return context.l10n.viewerTransitionParallax; + case ViewerTransition.fade: + return context.l10n.viewerTransitionFade; + case ViewerTransition.zoomIn: + return context.l10n.viewerTransitionZoomIn; + case ViewerTransition.none: + return context.l10n.viewerTransitionNone; + } + } +} + +extension ExtraWidgetDisplayedItemName on WidgetDisplayedItem { + String getName(BuildContext context) { + switch (this) { + case WidgetDisplayedItem.random: + return context.l10n.widgetDisplayedItemRandom; + case WidgetDisplayedItem.mostRecent: + return context.l10n.widgetDisplayedItemMostRecent; + } + } +} + +extension ExtraWidgetOpenPageName on WidgetOpenPage { + String getName(BuildContext context) { + switch (this) { + case WidgetOpenPage.home: + return context.l10n.widgetOpenPageHome; + case WidgetOpenPage.collection: + return context.l10n.widgetOpenPageCollection; + case WidgetOpenPage.viewer: + return context.l10n.widgetOpenPageViewer; + } + } +} diff --git a/lib/model/settings/enums/map_style.dart b/lib/model/settings/enums/map_style.dart index cedc7c71f..89e0e6e26 100644 --- a/lib/model/settings/enums/map_style.dart +++ b/lib/model/settings/enums/map_style.dart @@ -1,6 +1,4 @@ -import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves_map/aves_map.dart'; -import 'package:flutter/widgets.dart'; extension ExtraEntryMapStyle on EntryMapStyle { static bool isHeavy(EntryMapStyle? style) { @@ -16,27 +14,6 @@ extension ExtraEntryMapStyle on EntryMapStyle { } } - String getName(BuildContext context) { - switch (this) { - case EntryMapStyle.googleNormal: - return context.l10n.mapStyleGoogleNormal; - case EntryMapStyle.googleHybrid: - return context.l10n.mapStyleGoogleHybrid; - case EntryMapStyle.googleTerrain: - return context.l10n.mapStyleGoogleTerrain; - case EntryMapStyle.hmsNormal: - return context.l10n.mapStyleHuaweiNormal; - case EntryMapStyle.hmsTerrain: - return context.l10n.mapStyleHuaweiTerrain; - case EntryMapStyle.osmHot: - return context.l10n.mapStyleOsmHot; - case EntryMapStyle.stamenToner: - return context.l10n.mapStyleStamenToner; - case EntryMapStyle.stamenWatercolor: - return context.l10n.mapStyleStamenWatercolor; - } - } - bool get needMobileService { switch (this) { case EntryMapStyle.osmHot: diff --git a/lib/model/settings/enums/screen_on.dart b/lib/model/settings/enums/screen_on.dart index 5f21d4348..a2f168c64 100644 --- a/lib/model/settings/enums/screen_on.dart +++ b/lib/model/settings/enums/screen_on.dart @@ -1,23 +1,7 @@ +import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/services/common/services.dart'; -import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:flutter/widgets.dart'; - -import 'enums.dart'; extension ExtraKeepScreenOn on KeepScreenOn { - String getName(BuildContext context) { - switch (this) { - case KeepScreenOn.never: - return context.l10n.keepScreenOnNever; - case KeepScreenOn.videoPlayback: - return context.l10n.keepScreenOnVideoPlayback; - case KeepScreenOn.viewerOnly: - return context.l10n.keepScreenOnViewerOnly; - case KeepScreenOn.always: - return context.l10n.keepScreenOnAlways; - } - } - void apply() { windowService.keepScreenOn(this == KeepScreenOn.always); } diff --git a/lib/model/settings/enums/slideshow_video_playback.dart b/lib/model/settings/enums/slideshow_video_playback.dart deleted file mode 100644 index 453ddf29b..000000000 --- a/lib/model/settings/enums/slideshow_video_playback.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:flutter/widgets.dart'; - -import 'enums.dart'; - -extension ExtraSlideshowVideoPlayback on SlideshowVideoPlayback { - String getName(BuildContext context) { - switch (this) { - case SlideshowVideoPlayback.skip: - return context.l10n.videoPlaybackSkip; - case SlideshowVideoPlayback.playMuted: - return context.l10n.videoPlaybackMuted; - case SlideshowVideoPlayback.playWithSound: - return context.l10n.videoPlaybackWithSound; - } - } -} diff --git a/lib/model/settings/enums/subtitle_position.dart b/lib/model/settings/enums/subtitle_position.dart index 88d49d6f1..988545095 100644 --- a/lib/model/settings/enums/subtitle_position.dart +++ b/lib/model/settings/enums/subtitle_position.dart @@ -1,18 +1,7 @@ -import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/model/settings/enums/enums.dart'; import 'package:flutter/widgets.dart'; -import 'enums.dart'; - extension ExtraSubtitlePosition on SubtitlePosition { - String getName(BuildContext context) { - switch (this) { - case SubtitlePosition.top: - return context.l10n.subtitlePositionTop; - case SubtitlePosition.bottom: - return context.l10n.subtitlePositionBottom; - } - } - TextAlignVertical toTextAlignVertical() { switch (this) { case SubtitlePosition.top: diff --git a/lib/model/settings/enums/theme_brightness.dart b/lib/model/settings/enums/theme_brightness.dart index 5edf515ae..af38e11c6 100644 --- a/lib/model/settings/enums/theme_brightness.dart +++ b/lib/model/settings/enums/theme_brightness.dart @@ -1,22 +1,7 @@ -import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/model/settings/enums/enums.dart'; import 'package:flutter/material.dart'; -import 'enums.dart'; - extension ExtraAvesThemeBrightness on AvesThemeBrightness { - String getName(BuildContext context) { - switch (this) { - case AvesThemeBrightness.system: - return context.l10n.settingsSystemDefault; - case AvesThemeBrightness.light: - return context.l10n.themeBrightnessLight; - case AvesThemeBrightness.dark: - return context.l10n.themeBrightnessDark; - case AvesThemeBrightness.black: - return context.l10n.themeBrightnessBlack; - } - } - ThemeMode get appThemeMode { switch (this) { case AvesThemeBrightness.system: diff --git a/lib/model/settings/enums/thumbnail_overlay_location_icon.dart b/lib/model/settings/enums/thumbnail_overlay_location_icon.dart index e61a0fd37..c241fe853 100644 --- a/lib/model/settings/enums/thumbnail_overlay_location_icon.dart +++ b/lib/model/settings/enums/thumbnail_overlay_location_icon.dart @@ -1,21 +1,8 @@ +import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/theme/icons.dart'; -import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:flutter/material.dart'; - -import 'enums.dart'; +import 'package:flutter/widgets.dart'; extension ExtraThumbnailOverlayLocationIcon on ThumbnailOverlayLocationIcon { - String getName(BuildContext context) { - switch (this) { - case ThumbnailOverlayLocationIcon.located: - return context.l10n.filterLocatedLabel; - case ThumbnailOverlayLocationIcon.unlocated: - return context.l10n.filterNoLocationLabel; - case ThumbnailOverlayLocationIcon.none: - return context.l10n.settingsDisabled; - } - } - IconData getIcon(BuildContext context) { switch (this) { case ThumbnailOverlayLocationIcon.unlocated: diff --git a/lib/model/settings/enums/thumbnail_overlay_tag_icon.dart b/lib/model/settings/enums/thumbnail_overlay_tag_icon.dart index 00f006ec9..969900e82 100644 --- a/lib/model/settings/enums/thumbnail_overlay_tag_icon.dart +++ b/lib/model/settings/enums/thumbnail_overlay_tag_icon.dart @@ -1,21 +1,8 @@ +import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/theme/icons.dart'; -import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:flutter/material.dart'; - -import 'enums.dart'; +import 'package:flutter/widgets.dart'; extension ExtraThumbnailOverlayTagIcon on ThumbnailOverlayTagIcon { - String getName(BuildContext context) { - switch (this) { - case ThumbnailOverlayTagIcon.tagged: - return context.l10n.filterTaggedLabel; - case ThumbnailOverlayTagIcon.untagged: - return context.l10n.filterNoTagLabel; - case ThumbnailOverlayTagIcon.none: - return context.l10n.settingsDisabled; - } - } - IconData getIcon(BuildContext context) { switch (this) { case ThumbnailOverlayTagIcon.tagged: diff --git a/lib/model/settings/enums/unit_system.dart b/lib/model/settings/enums/unit_system.dart deleted file mode 100644 index cde99328b..000000000 --- a/lib/model/settings/enums/unit_system.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:flutter/widgets.dart'; - -import 'enums.dart'; - -extension ExtraUnitSystem on UnitSystem { - String getName(BuildContext context) { - switch (this) { - case UnitSystem.metric: - return context.l10n.unitSystemMetric; - case UnitSystem.imperial: - return context.l10n.unitSystemImperial; - } - } -} diff --git a/lib/model/settings/enums/video_auto_play_mode.dart b/lib/model/settings/enums/video_auto_play_mode.dart deleted file mode 100644 index b06c765dd..000000000 --- a/lib/model/settings/enums/video_auto_play_mode.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:flutter/widgets.dart'; - -import 'enums.dart'; - -extension ExtraVideoAutoPlayMode on VideoAutoPlayMode { - String getName(BuildContext context) { - switch (this) { - case VideoAutoPlayMode.disabled: - return context.l10n.settingsDisabled; - case VideoAutoPlayMode.playMuted: - return context.l10n.videoPlaybackMuted; - case VideoAutoPlayMode.playWithSound: - return context.l10n.videoPlaybackWithSound; - } - } -} diff --git a/lib/model/settings/enums/video_background_mode.dart b/lib/model/settings/enums/video_background_mode.dart deleted file mode 100644 index 0a7e040bb..000000000 --- a/lib/model/settings/enums/video_background_mode.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:flutter/widgets.dart'; - -import 'enums.dart'; - -extension ExtraVideoBackgroundMode on VideoBackgroundMode { - String getName(BuildContext context) { - switch (this) { - case VideoBackgroundMode.disabled: - return context.l10n.settingsDisabled; - case VideoBackgroundMode.pip: - return context.l10n.settingsVideoEnablePip; - } - } -} diff --git a/lib/model/settings/enums/video_controls.dart b/lib/model/settings/enums/video_controls.dart deleted file mode 100644 index 6f9979f71..000000000 --- a/lib/model/settings/enums/video_controls.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:flutter/widgets.dart'; - -import 'enums.dart'; - -extension ExtraVideoControls on VideoControls { - String getName(BuildContext context) { - switch (this) { - case VideoControls.play: - return context.l10n.videoControlsPlay; - case VideoControls.playSeek: - return context.l10n.videoControlsPlaySeek; - case VideoControls.playOutside: - return context.l10n.videoControlsPlayOutside; - case VideoControls.none: - return context.l10n.videoControlsNone; - } - } -} diff --git a/lib/model/settings/enums/video_loop_mode.dart b/lib/model/settings/enums/video_loop_mode.dart index 255e9ccde..0799fcfe8 100644 --- a/lib/model/settings/enums/video_loop_mode.dart +++ b/lib/model/settings/enums/video_loop_mode.dart @@ -1,20 +1,6 @@ -import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:flutter/widgets.dart'; - -import 'enums.dart'; +import 'package:aves/model/settings/enums/enums.dart'; extension ExtraVideoLoopMode on VideoLoopMode { - String getName(BuildContext context) { - switch (this) { - case VideoLoopMode.never: - return context.l10n.videoLoopModeNever; - case VideoLoopMode.shortOnly: - return context.l10n.videoLoopModeShortOnly; - case VideoLoopMode.always: - return context.l10n.videoLoopModeAlways; - } - } - static const shortVideoThreshold = Duration(seconds: 30); bool shouldLoop(int? durationMillis) { diff --git a/lib/model/settings/enums/viewer_transition.dart b/lib/model/settings/enums/viewer_transition.dart index 739e067c3..1f4ff4352 100644 --- a/lib/model/settings/enums/viewer_transition.dart +++ b/lib/model/settings/enums/viewer_transition.dart @@ -1,25 +1,8 @@ -import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/widgets/viewer/controls/controller.dart'; import 'package:flutter/widgets.dart'; -import 'enums.dart'; - extension ExtraViewerTransition on ViewerTransition { - String getName(BuildContext context) { - switch (this) { - case ViewerTransition.slide: - return context.l10n.viewerTransitionSlide; - case ViewerTransition.parallax: - return context.l10n.viewerTransitionParallax; - case ViewerTransition.fade: - return context.l10n.viewerTransitionFade; - case ViewerTransition.zoomIn: - return context.l10n.viewerTransitionZoomIn; - case ViewerTransition.none: - return context.l10n.viewerTransitionNone; - } - } - TransitionBuilder builder(PageController pageController, int index) { switch (this) { case ViewerTransition.slide: diff --git a/lib/model/settings/enums/widget_displayed_item.dart b/lib/model/settings/enums/widget_displayed_item.dart deleted file mode 100644 index 0914d4a6a..000000000 --- a/lib/model/settings/enums/widget_displayed_item.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:aves/model/settings/enums/enums.dart'; -import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:flutter/widgets.dart'; - -extension ExtraWidgetDisplayedItem on WidgetDisplayedItem { - String getName(BuildContext context) { - switch (this) { - case WidgetDisplayedItem.random: - return context.l10n.widgetDisplayedItemRandom; - case WidgetDisplayedItem.mostRecent: - return context.l10n.widgetDisplayedItemMostRecent; - } - } -} diff --git a/lib/model/settings/enums/widget_open_action.dart b/lib/model/settings/enums/widget_open_action.dart deleted file mode 100644 index 333be5fca..000000000 --- a/lib/model/settings/enums/widget_open_action.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:aves/model/settings/enums/enums.dart'; -import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:flutter/widgets.dart'; - -extension ExtraWidgetOpenPage on WidgetOpenPage { - String getName(BuildContext context) { - switch (this) { - case WidgetOpenPage.home: - return context.l10n.widgetOpenPageHome; - case WidgetOpenPage.collection: - return context.l10n.widgetOpenPageCollection; - case WidgetOpenPage.viewer: - return context.l10n.widgetOpenPageViewer; - } - } -} diff --git a/lib/model/settings/enums/widget_shape.dart b/lib/model/settings/enums/widget_shape.dart index c56a0debd..deeed752a 100644 --- a/lib/model/settings/enums/widget_shape.dart +++ b/lib/model/settings/enums/widget_shape.dart @@ -1,6 +1,6 @@ import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/settings/enums/enums.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; extension ExtraWidgetShape on WidgetShape { Path path(Size widgetSize, double devicePixelRatio) { diff --git a/lib/model/source/collection_lens.dart b/lib/model/source/collection_lens.dart index 801763194..a117a8fdc 100644 --- a/lib/model/source/collection_lens.dart +++ b/lib/model/source/collection_lens.dart @@ -15,17 +15,16 @@ import 'package:aves/model/filters/rating.dart'; import 'package:aves/model/filters/trash.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/model/source/events.dart'; import 'package:aves/model/source/location/location.dart'; import 'package:aves/model/source/section_keys.dart'; import 'package:aves/model/source/tag.dart'; -import 'package:aves_utils/aves_utils.dart'; import 'package:aves/utils/collection_utils.dart'; +import 'package:aves_utils/aves_utils.dart'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; -import 'enums/enums.dart'; - class CollectionLens with ChangeNotifier { final CollectionSource source; final Set filters; diff --git a/lib/model/source/enums/view.dart b/lib/model/source/enums/view.dart index aab4397f7..7f6c50ffd 100644 --- a/lib/model/source/enums/view.dart +++ b/lib/model/source/enums/view.dart @@ -1,9 +1,8 @@ +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:flutter/widgets.dart'; -import 'enums.dart'; - extension ExtraEntrySortFactor on EntrySortFactor { String getName(BuildContext context) { final l10n = context.l10n; diff --git a/lib/widgets/common/map/geo_map.dart b/lib/widgets/common/map/geo_map.dart index af2a37f98..0a2e8691e 100644 --- a/lib/widgets/common/map/geo_map.dart +++ b/lib/widgets/common/map/geo_map.dart @@ -3,15 +3,15 @@ import 'dart:math'; import 'dart:ui'; import 'package:aves/model/entry/entry.dart'; -import 'package:aves/model/entry/extensions/location.dart'; import 'package:aves/model/entry/extensions/images.dart'; +import 'package:aves/model/entry/extensions/location.dart'; import 'package:aves/model/entry/sort.dart'; +import 'package:aves/model/settings/enums/l10n.dart'; import 'package:aves/model/settings/enums/map_style.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; -import 'package:aves_utils/aves_utils.dart'; import 'package:aves/utils/constants.dart'; import 'package:aves/utils/math_utils.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; @@ -23,6 +23,7 @@ import 'package:aves/widgets/common/map/leaflet/map.dart'; import 'package:aves/widgets/common/thumbnail/image.dart'; import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; import 'package:aves_map/aves_map.dart'; +import 'package:aves_utils/aves_utils.dart'; import 'package:collection/collection.dart'; import 'package:fluster/fluster.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/common/map/map_action_delegate.dart b/lib/widgets/common/map/map_action_delegate.dart index 06a984005..a41c840d5 100644 --- a/lib/widgets/common/map/map_action_delegate.dart +++ b/lib/widgets/common/map/map_action_delegate.dart @@ -1,5 +1,5 @@ import 'package:aves/model/actions/map_actions.dart'; -import 'package:aves/model/settings/enums/map_style.dart'; +import 'package:aves/model/settings/enums/l10n.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; diff --git a/lib/widgets/settings/accessibility/accessibility.dart b/lib/widgets/settings/accessibility/accessibility.dart index 484414f3a..17d963d7f 100644 --- a/lib/widgets/settings/accessibility/accessibility.dart +++ b/lib/widgets/settings/accessibility/accessibility.dart @@ -1,7 +1,7 @@ import 'dart:async'; -import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/enums/enums.dart'; +import 'package:aves/model/settings/enums/l10n.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/colors.dart'; import 'package:aves/theme/icons.dart'; diff --git a/lib/widgets/settings/accessibility/time_to_take_action.dart b/lib/widgets/settings/accessibility/time_to_take_action.dart index 950eb76ec..137867c57 100644 --- a/lib/widgets/settings/accessibility/time_to_take_action.dart +++ b/lib/widgets/settings/accessibility/time_to_take_action.dart @@ -1,5 +1,5 @@ -import 'package:aves/model/settings/enums/accessibility_timeout.dart'; import 'package:aves/model/settings/enums/enums.dart'; +import 'package:aves/model/settings/enums/l10n.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/services/accessibility_service.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; diff --git a/lib/widgets/settings/display/display.dart b/lib/widgets/settings/display/display.dart index 959249993..e5f9924dc 100644 --- a/lib/widgets/settings/display/display.dart +++ b/lib/widgets/settings/display/display.dart @@ -1,9 +1,8 @@ import 'dart:async'; import 'package:aves/model/device.dart'; -import 'package:aves/model/settings/enums/display_refresh_rate_mode.dart'; import 'package:aves/model/settings/enums/enums.dart'; -import 'package:aves/model/settings/enums/theme_brightness.dart'; +import 'package:aves/model/settings/enums/l10n.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/colors.dart'; import 'package:aves/theme/icons.dart'; diff --git a/lib/widgets/settings/home_widget_settings_page.dart b/lib/widgets/settings/home_widget_settings_page.dart index a0c1341c6..8be4d42c8 100644 --- a/lib/widgets/settings/home_widget_settings_page.dart +++ b/lib/widgets/settings/home_widget_settings_page.dart @@ -1,7 +1,6 @@ import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/enums/enums.dart'; -import 'package:aves/model/settings/enums/widget_displayed_item.dart'; -import 'package:aves/model/settings/enums/widget_open_action.dart'; +import 'package:aves/model/settings/enums/l10n.dart'; import 'package:aves/model/settings/enums/widget_shape.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/services/widget_service.dart'; diff --git a/lib/widgets/settings/language/language.dart b/lib/widgets/settings/language/language.dart index ff33641f5..472c2b990 100644 --- a/lib/widgets/settings/language/language.dart +++ b/lib/widgets/settings/language/language.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:aves/model/settings/enums/coordinate_format.dart'; import 'package:aves/model/settings/enums/enums.dart'; -import 'package:aves/model/settings/enums/unit_system.dart'; +import 'package:aves/model/settings/enums/l10n.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/colors.dart'; import 'package:aves/theme/icons.dart'; diff --git a/lib/widgets/settings/navigation/navigation.dart b/lib/widgets/settings/navigation/navigation.dart index 2c738b136..93ea8a347 100644 --- a/lib/widgets/settings/navigation/navigation.dart +++ b/lib/widgets/settings/navigation/navigation.dart @@ -1,8 +1,7 @@ import 'dart:async'; import 'package:aves/model/settings/enums/enums.dart'; -import 'package:aves/model/settings/enums/home_page.dart'; -import 'package:aves/model/settings/enums/screen_on.dart'; +import 'package:aves/model/settings/enums/l10n.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/colors.dart'; import 'package:aves/theme/icons.dart'; diff --git a/lib/widgets/settings/screen_saver_settings_page.dart b/lib/widgets/settings/screen_saver_settings_page.dart index 8d7902244..aed3abf0f 100644 --- a/lib/widgets/settings/screen_saver_settings_page.dart +++ b/lib/widgets/settings/screen_saver_settings_page.dart @@ -1,7 +1,6 @@ import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/enums/enums.dart'; -import 'package:aves/model/settings/enums/slideshow_video_playback.dart'; -import 'package:aves/model/settings/enums/viewer_transition.dart'; +import 'package:aves/model/settings/enums/l10n.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/widgets/common/basic/scaffold.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; diff --git a/lib/widgets/settings/thumbnails/overlay.dart b/lib/widgets/settings/thumbnails/overlay.dart index b8c47950e..aa1bef264 100644 --- a/lib/widgets/settings/thumbnails/overlay.dart +++ b/lib/widgets/settings/thumbnails/overlay.dart @@ -1,4 +1,5 @@ import 'package:aves/model/settings/enums/enums.dart'; +import 'package:aves/model/settings/enums/l10n.dart'; import 'package:aves/model/settings/enums/thumbnail_overlay_location_icon.dart'; import 'package:aves/model/settings/enums/thumbnail_overlay_tag_icon.dart'; import 'package:aves/model/settings/settings.dart'; diff --git a/lib/widgets/settings/video/controls.dart b/lib/widgets/settings/video/controls.dart index cbe4423b9..e8ee34c9c 100644 --- a/lib/widgets/settings/video/controls.dart +++ b/lib/widgets/settings/video/controls.dart @@ -1,5 +1,5 @@ import 'package:aves/model/settings/enums/enums.dart'; -import 'package:aves/model/settings/enums/video_controls.dart'; +import 'package:aves/model/settings/enums/l10n.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/widgets/common/basic/scaffold.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; diff --git a/lib/widgets/settings/video/subtitle_theme.dart b/lib/widgets/settings/video/subtitle_theme.dart index 5121d0377..6fc3fd7e2 100644 --- a/lib/widgets/settings/video/subtitle_theme.dart +++ b/lib/widgets/settings/video/subtitle_theme.dart @@ -1,5 +1,5 @@ import 'package:aves/model/settings/enums/enums.dart'; -import 'package:aves/model/settings/enums/subtitle_position.dart'; +import 'package:aves/model/settings/enums/l10n.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/widgets/common/basic/list_tiles/color.dart'; import 'package:aves/widgets/common/basic/list_tiles/slider.dart'; diff --git a/lib/widgets/settings/video/video.dart b/lib/widgets/settings/video/video.dart index c0e2e4acc..ba68e3d6b 100644 --- a/lib/widgets/settings/video/video.dart +++ b/lib/widgets/settings/video/video.dart @@ -3,9 +3,7 @@ import 'dart:async'; import 'package:aves/model/device.dart'; import 'package:aves/model/filters/mime.dart'; import 'package:aves/model/settings/enums/enums.dart'; -import 'package:aves/model/settings/enums/video_auto_play_mode.dart'; -import 'package:aves/model/settings/enums/video_background_mode.dart'; -import 'package:aves/model/settings/enums/video_loop_mode.dart'; +import 'package:aves/model/settings/enums/l10n.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/colors.dart'; import 'package:aves/theme/icons.dart'; diff --git a/lib/widgets/settings/viewer/slideshow.dart b/lib/widgets/settings/viewer/slideshow.dart index b2f954c23..f84cab854 100644 --- a/lib/widgets/settings/viewer/slideshow.dart +++ b/lib/widgets/settings/viewer/slideshow.dart @@ -1,6 +1,5 @@ import 'package:aves/model/settings/enums/enums.dart'; -import 'package:aves/model/settings/enums/slideshow_video_playback.dart'; -import 'package:aves/model/settings/enums/viewer_transition.dart'; +import 'package:aves/model/settings/enums/l10n.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/widgets/common/basic/scaffold.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; From 4e86d2f1fe56f16ab80261baafdfb58eb357e0a1 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Thu, 16 Mar 2023 16:15:19 +0100 Subject: [PATCH 07/11] fixed playing video from app content provider --- CHANGELOG.md | 4 ++++ lib/widgets/home_page.dart | 10 ++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 706a7352f..1f47c44c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Fixed + +- Viewer: playing video from app content provider + ## [v1.8.3] - 2023-03-13 ### Added diff --git a/lib/widgets/home_page.dart b/lib/widgets/home_page.dart index feb55bf88..52ba45a4e 100644 --- a/lib/widgets/home_page.dart +++ b/lib/widgets/home_page.dart @@ -228,11 +228,12 @@ class _HomePageState extends State { canAnalyze: false, ); } + } else { + await _initViewerEssentials(); } break; case AppMode.setWallpaper: - // for video playback storage - await metadataDb.init(); + await _initViewerEssentials(); break; case AppMode.pickMediaInternal: case AppMode.pickFilterInternal: @@ -248,6 +249,11 @@ class _HomePageState extends State { )); } + Future _initViewerEssentials() async { + // for video playback storage + await metadataDb.init(); + } + bool _isViewerSourceable(AvesEntry? viewerEntry) { return viewerEntry != null && viewerEntry.directory != null && !settings.hiddenFilters.any((filter) => filter.test(viewerEntry)); } From bd658bd3dbff261acde1127d32a80abdc29aadfb Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Thu, 16 Mar 2023 17:05:45 +0100 Subject: [PATCH 08/11] pub upgrade --- plugins/aves_report_crashlytics/pubspec.lock | 16 +-- plugins/aves_utils/pubspec.lock | 102 ------------------- plugins/aves_video/pubspec.lock | 8 -- pubspec.lock | 18 ++-- 4 files changed, 17 insertions(+), 127 deletions(-) diff --git a/plugins/aves_report_crashlytics/pubspec.lock b/plugins/aves_report_crashlytics/pubspec.lock index 04cf57c9f..e3180cc2c 100644 --- a/plugins/aves_report_crashlytics/pubspec.lock +++ b/plugins/aves_report_crashlytics/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: cb3a948a1eebbf8efd987c43f95418269930e912a88bc7b6a5a7658423133635 + sha256: "330d7fcbb72624f5b6d374af8b059b0ef4ba96ba5b8987f874964a1287eb617d" url: "https://pub.dev" source: hosted - version: "1.0.17" + version: "1.0.18" async: dependency: transitive description: @@ -68,10 +68,10 @@ packages: dependency: "direct main" description: name: firebase_core - sha256: "1c121a478af23755b0b93fd4aa70d3bd32a587dd51ef0a3979091ac0d2317d32" + sha256: "75f747cafd7cbd6c00b908e3a7aa59fc31593d46ba8165d9ee8a79e69464a394" url: "https://pub.dev" source: hosted - version: "2.7.1" + version: "2.8.0" firebase_core_platform_interface: dependency: transitive description: @@ -92,18 +92,18 @@ packages: dependency: "direct main" description: name: firebase_crashlytics - sha256: d4190479611f42a34a04630d9188f30b592b65013d0f19c408d5249998429864 + sha256: "5410b6ab2009fc6c181ca82e16525fdcb324c5851933bfbb49246543b1005ebc" url: "https://pub.dev" source: hosted - version: "3.0.16" + version: "3.0.17" firebase_crashlytics_platform_interface: dependency: transitive description: name: firebase_crashlytics_platform_interface - sha256: "667955b348b7729ed3d987956021a97fcdd05957df7c27b2d855e6b3f70e9521" + sha256: bf3d3791c51a8448413b5ebd8388fa7db239a3e165db6bbd8ab58ef0e8f61906 url: "https://pub.dev" source: hosted - version: "3.3.16" + version: "3.3.17" flutter: dependency: "direct main" description: flutter diff --git a/plugins/aves_utils/pubspec.lock b/plugins/aves_utils/pubspec.lock index b61cdad6b..6e38c33fc 100644 --- a/plugins/aves_utils/pubspec.lock +++ b/plugins/aves_utils/pubspec.lock @@ -1,22 +1,6 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: - async: - dependency: transitive - description: - name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 - url: "https://pub.dev" - source: hosted - version: "2.10.0" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" - url: "https://pub.dev" - source: hosted - version: "2.1.1" characters: dependency: transitive description: @@ -25,14 +9,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" - clock: - dependency: transitive - description: - name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf - url: "https://pub.dev" - source: hosted - version: "1.1.1" collection: dependency: transitive description: @@ -41,14 +17,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.17.0" - fake_async: - dependency: transitive - description: - name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" - url: "https://pub.dev" - source: hosted - version: "1.3.1" flutter: dependency: "direct main" description: flutter @@ -62,11 +30,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" js: dependency: transitive description: @@ -83,14 +46,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" - matcher: - dependency: transitive - description: - name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" - url: "https://pub.dev" - source: hosted - version: "0.12.13" material_color_utilities: dependency: transitive description: @@ -107,67 +62,11 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.0" - path: - dependency: transitive - description: - name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b - url: "https://pub.dev" - source: hosted - version: "1.8.2" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" - source_span: - dependency: transitive - description: - name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 - url: "https://pub.dev" - source: hosted - version: "1.9.1" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 - url: "https://pub.dev" - source: hosted - version: "1.11.0" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" - url: "https://pub.dev" - source: hosted - version: "1.2.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 - url: "https://pub.dev" - source: hosted - version: "1.2.1" - test_api: - dependency: transitive - description: - name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 - url: "https://pub.dev" - source: hosted - version: "0.4.16" vector_math: dependency: transitive description: @@ -178,4 +77,3 @@ packages: version: "2.1.4" sdks: dart: ">=2.19.4 <3.0.0" - flutter: ">=1.17.0" diff --git a/plugins/aves_video/pubspec.lock b/plugins/aves_video/pubspec.lock index 1e86a4ad9..3902a0a45 100644 --- a/plugins/aves_video/pubspec.lock +++ b/plugins/aves_video/pubspec.lock @@ -24,14 +24,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.17.0" - equatable: - dependency: transitive - description: - name: equatable - sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 - url: "https://pub.dev" - source: hosted - version: "2.0.5" flutter: dependency: "direct main" description: flutter diff --git a/pubspec.lock b/pubspec.lock index 26c878c58..9aa79cb26 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: cb3a948a1eebbf8efd987c43f95418269930e912a88bc7b6a5a7658423133635 + sha256: "330d7fcbb72624f5b6d374af8b059b0ef4ba96ba5b8987f874964a1287eb617d" url: "https://pub.dev" source: hosted - version: "1.0.17" + version: "1.0.18" analyzer: dependency: transitive description: @@ -326,7 +326,7 @@ packages: description: path: "." ref: aves - resolved-ref: "69193da9a6b7f8b5cec5627253982adc303dbf46" + resolved-ref: "1ff8e6d82466939997c0e04a8e28b04f40641728" url: "https://github.com/deckerst/fijkplayer.git" source: git version: "0.10.0" @@ -342,10 +342,10 @@ packages: dependency: transitive description: name: firebase_core - sha256: "1c121a478af23755b0b93fd4aa70d3bd32a587dd51ef0a3979091ac0d2317d32" + sha256: "75f747cafd7cbd6c00b908e3a7aa59fc31593d46ba8165d9ee8a79e69464a394" url: "https://pub.dev" source: hosted - version: "2.7.1" + version: "2.8.0" firebase_core_platform_interface: dependency: transitive description: @@ -366,18 +366,18 @@ packages: dependency: transitive description: name: firebase_crashlytics - sha256: d4190479611f42a34a04630d9188f30b592b65013d0f19c408d5249998429864 + sha256: "5410b6ab2009fc6c181ca82e16525fdcb324c5851933bfbb49246543b1005ebc" url: "https://pub.dev" source: hosted - version: "3.0.16" + version: "3.0.17" firebase_crashlytics_platform_interface: dependency: transitive description: name: firebase_crashlytics_platform_interface - sha256: "667955b348b7729ed3d987956021a97fcdd05957df7c27b2d855e6b3f70e9521" + sha256: bf3d3791c51a8448413b5ebd8388fa7db239a3e165db6bbd8ab58ef0e8f61906 url: "https://pub.dev" source: hosted - version: "3.3.16" + version: "3.3.17" flex_color_picker: dependency: "direct main" description: From 298637a3c644b13f5a6210f44d989f2dff65ccb9 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Thu, 16 Mar 2023 19:35:35 +0100 Subject: [PATCH 09/11] TV: improved support for Licenses --- CHANGELOG.md | 4 + lib/widgets/about/licenses.dart | 3 +- lib/widgets/about/tv_license_page.dart | 357 ++++++++++++++++++++++++ lib/widgets/settings/settings_page.dart | 11 +- pubspec.yaml | 4 + 5 files changed, 376 insertions(+), 3 deletions(-) create mode 100644 lib/widgets/about/tv_license_page.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f47c44c1..9f97ffd6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added + +- TV: improved support for Licenses + ### Fixed - Viewer: playing video from app content provider diff --git a/lib/widgets/about/licenses.dart b/lib/widgets/about/licenses.dart index ac1f1b1f1..e3d476d58 100644 --- a/lib/widgets/about/licenses.dart +++ b/lib/widgets/about/licenses.dart @@ -4,6 +4,7 @@ import 'package:aves/ref/brand_colors.dart'; import 'package:aves/theme/colors.dart'; import 'package:aves/utils/dependencies.dart'; import 'package:aves/widgets/about/title.dart'; +import 'package:aves/widgets/about/tv_license_page.dart'; import 'package:aves/widgets/common/basic/link_chip.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/aves_expansion_tile.dart'; @@ -87,7 +88,7 @@ class _LicensesState extends State { // as of Flutter v1.22.4, `cardColor` is used as a background color by `LicensePage` cardColor: Theme.of(context).scaffoldBackgroundColor, ), - child: const LicensePage(), + child: settings.useTvLayout ? const TvLicensePage() : const LicensePage(), ), ), ), diff --git a/lib/widgets/about/tv_license_page.dart b/lib/widgets/about/tv_license_page.dart new file mode 100644 index 000000000..abd54b1c8 --- /dev/null +++ b/lib/widgets/about/tv_license_page.dart @@ -0,0 +1,357 @@ +import 'package:aves/widgets/common/basic/scaffold.dart'; +import 'package:aves/widgets/common/behaviour/intents.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter/services.dart'; + +// as of Flutter v3.7.7, `LicensePage` is not designed for Android TV +// and gets rejected from Google Play review: +// ``` +// Your app’s text is cut off at the edge of the screen. +// Apps should not display any text or functionality that is partially cut off by the edges of the screen. +// For example, your app (version code 94) in the "Show All Licenses" section text is cut off from the bottom of the screen. +// ``` +class TvLicensePage extends StatefulWidget { + const TvLicensePage({super.key}); + + @override + State createState() => _TvLicensePageState(); +} + +class _TvLicensePageState extends State { + final FocusNode _railFocusNode = FocusNode(); + final ScrollController _detailsScrollController = ScrollController(); + final ValueNotifier _railIndexNotifier = ValueNotifier(0); + + final Future<_LicenseData> licenses = LicenseRegistry.licenses + .fold<_LicenseData>( + _LicenseData(), + (prev, license) => prev..addLicense(license), + ) + .then((licenseData) => licenseData..sortPackages()); + + @override + void dispose() { + _railIndexNotifier.dispose(); + _railFocusNode.dispose(); + _detailsScrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AvesScaffold( + appBar: AppBar( + automaticallyImplyLeading: false, + title: Text(MaterialLocalizations.of(context).licensesPageTitle), + ), + body: ValueListenableBuilder( + valueListenable: _railIndexNotifier, + builder: (context, selectedIndex, child) { + return FutureBuilder<_LicenseData>( + future: licenses, + builder: (context, snapshot) { + final data = snapshot.data; + if (data == null) { + return const Center( + child: CircularProgressIndicator(), + ); + } + + final packages = data.packages; + final rail = Focus( + focusNode: _railFocusNode, + skipTraversal: true, + canRequestFocus: false, + child: ConstrainedBox( + constraints: BoxConstraints.loose(const Size.fromWidth(300)), + child: ListView.builder( + itemBuilder: (context, index) { + final packageName = packages[index]; + final bindings = data.packageLicenseBindings[packageName]!; + final isSelected = index == selectedIndex; + return Ink( + color: isSelected ? Theme.of(context).highlightColor : Theme.of(context).cardColor, + child: ListTile( + title: Text(packageName), + subtitle: Text(MaterialLocalizations.of(context).licensesPackageDetailText(bindings.length)), + selected: isSelected, + onTap: () => _railIndexNotifier.value = index, + ), + ); + }, + itemCount: packages.length, + ), + ), + ); + + final packageName = packages[selectedIndex]; + final bindings = data.packageLicenseBindings[packageName]!; + + return SafeArea( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(width: 16), + rail, + Expanded( + child: FocusableActionDetector( + shortcuts: const { + SingleActivator(LogicalKeyboardKey.arrowUp): ScrollIntent(direction: AxisDirection.up, type: ScrollIncrementType.page), + SingleActivator(LogicalKeyboardKey.arrowDown): ScrollIntent(direction: AxisDirection.down, type: ScrollIncrementType.page), + }, + actions: { + ScrollIntent: ScrollControllerAction(scrollController: _detailsScrollController), + }, + child: KeyedSubtree( + key: Key(packageName), + child: _PackageLicensePage( + packageName: packageName, + licenseEntries: bindings.map((i) => data.licenses[i]).toList(growable: false), + scrollController: _detailsScrollController, + ), + ), + ), + ), + ], + ), + ); + }, + ); + }, + ), + ); + } +} + +// adapted from Flutter `_LicenseData` in `/material/about.dart` +class _LicenseData { + final List licenses = []; + final Map> packageLicenseBindings = >{}; + final List packages = []; + + // Special treatment for the first package since it should be the package + // for delivered application. + String? firstPackage; + + void addLicense(LicenseEntry entry) { + // Before the license can be added, we must first record the packages to + // which it belongs. + for (final String package in entry.packages) { + _addPackage(package); + // Bind this license to the package using the next index value. This + // creates a contract that this license must be inserted at this same + // index value. + packageLicenseBindings[package]!.add(licenses.length); + } + licenses.add(entry); // Completion of the contract above. + } + + /// Add a package and initialize package license binding. This is a no-op if + /// the package has been seen before. + void _addPackage(String package) { + if (!packageLicenseBindings.containsKey(package)) { + packageLicenseBindings[package] = []; + firstPackage ??= package; + packages.add(package); + } + } + + /// Sort the packages using some comparison method, or by the default manner, + /// which is to put the application package first, followed by every other + /// package in case-insensitive alphabetical order. + void sortPackages([int Function(String a, String b)? compare]) { + packages.sort(compare ?? + (a, b) { + // Based on how LicenseRegistry currently behaves, the first package + // returned is the end user application license. This should be + // presented first in the list. So here we make sure that first package + // remains at the front regardless of alphabetical sorting. + if (a == firstPackage) { + return -1; + } + if (b == firstPackage) { + return 1; + } + return a.toLowerCase().compareTo(b.toLowerCase()); + }); + } +} + +// adapted from Flutter `_PackageLicensePage` in `/material/about.dart` +class _PackageLicensePage extends StatefulWidget { + const _PackageLicensePage({ + required this.packageName, + required this.licenseEntries, + required this.scrollController, + }); + + final String packageName; + final List licenseEntries; + final ScrollController? scrollController; + + @override + _PackageLicensePageState createState() => _PackageLicensePageState(); +} + +class _PackageLicensePageState extends State<_PackageLicensePage> { + @override + void initState() { + super.initState(); + _initLicenses(); + } + + final List _licenses = []; + bool _loaded = false; + + Future _initLicenses() async { + for (final LicenseEntry license in widget.licenseEntries) { + if (!mounted) { + return; + } + final List paragraphs = await SchedulerBinding.instance.scheduleTask>( + license.paragraphs.toList, + Priority.animation, + debugLabel: 'License', + ); + if (!mounted) { + return; + } + setState(() { + _licenses.add(const Padding( + padding: EdgeInsets.all(18.0), + child: Divider(), + )); + for (final LicenseParagraph paragraph in paragraphs) { + if (paragraph.indent == LicenseParagraph.centeredIndent) { + _licenses.add(Padding( + padding: const EdgeInsets.only(top: 16.0), + child: Text( + paragraph.text, + style: const TextStyle(fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), + )); + } else { + _licenses.add(Padding( + padding: EdgeInsetsDirectional.only(top: 8.0, start: 16.0 * paragraph.indent), + child: Text(paragraph.text), + )); + } + } + }); + } + setState(() { + _loaded = true; + }); + } + + @override + Widget build(BuildContext context) { + final MaterialLocalizations localizations = MaterialLocalizations.of(context); + final ThemeData theme = Theme.of(context); + final String title = widget.packageName; + final String subtitle = localizations.licensesPackageDetailText(widget.licenseEntries.length); + const double pad = 24; + const EdgeInsets padding = EdgeInsets.only(left: pad, right: pad, bottom: pad); + final List listWidgets = [ + ..._licenses, + if (!_loaded) + const Padding( + padding: EdgeInsets.symmetric(vertical: 24.0), + child: Center( + child: CircularProgressIndicator(), + ), + ), + ]; + + final Widget page; + if (widget.scrollController == null) { + page = Scaffold( + appBar: AppBar( + title: _PackageLicensePageTitle( + title, + subtitle, + theme.primaryTextTheme, + ), + ), + body: Center( + child: Material( + color: theme.cardColor, + elevation: 4.0, + child: Container( + constraints: BoxConstraints.loose(const Size.fromWidth(600.0)), + child: Localizations.override( + locale: const Locale('en', 'US'), + context: context, + child: ScrollConfiguration( + // A Scrollbar is built-in below. + behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false), + child: Scrollbar( + child: ListView(padding: padding, children: listWidgets), + ), + ), + ), + ), + ), + ), + ); + } else { + page = CustomScrollView( + controller: widget.scrollController, + slivers: [ + SliverAppBar( + automaticallyImplyLeading: false, + pinned: true, + backgroundColor: theme.cardColor, + title: _PackageLicensePageTitle(title, subtitle, theme.textTheme), + ), + SliverPadding( + padding: padding, + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => Localizations.override( + locale: const Locale('en', 'US'), + context: context, + child: listWidgets[index], + ), + childCount: listWidgets.length, + ), + ), + ), + ], + ); + } + return DefaultTextStyle( + style: theme.textTheme.bodySmall!, + child: page, + ); + } +} + +class _PackageLicensePageTitle extends StatelessWidget { + const _PackageLicensePageTitle( + this.title, + this.subtitle, + this.theme, + ); + + final String title; + final String subtitle; + final TextTheme theme; + + @override + Widget build(BuildContext context) { + final Color? color = Theme.of(context).appBarTheme.foregroundColor; + + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, style: theme.titleLarge?.copyWith(color: color)), + Text(subtitle, style: theme.titleSmall?.copyWith(color: color)), + ], + ); + } +} diff --git a/lib/widgets/settings/settings_page.dart b/lib/widgets/settings/settings_page.dart index cdb23c642..c917ffcde 100644 --- a/lib/widgets/settings/settings_page.dart +++ b/lib/widgets/settings/settings_page.dart @@ -97,8 +97,15 @@ class _SettingsPageState extends State with FeedbackMixin { primary: false, ), ), - const Expanded( - child: _TvRail(), + Expanded( + child: MediaQuery.removePadding( + context: context, + removeLeft: true, + removeTop: true, + removeRight: true, + removeBottom: true, + child: const _TvRail(), + ), ), ], ), diff --git a/pubspec.yaml b/pubspec.yaml index 5357ab73f..22289c7d3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -163,6 +163,10 @@ flutter: # `OutputBuffer` in `/services/common/output_buffer.dart` # adapts from Flutter v3.3.3 `_OutputBuffer` in `/foundation/consolidate_response.dart` # +# `TvLicensePage` in `/widgets/about/tv_license_page.dart` +# adapts from Flutter v3.7.7 `_LicenseData` in `/material/about.dart` +# and `_PackageLicensePage` in `/material/about.dart` +# # `OverlaySnackBar` in `/widgets/common/action_mixins/overlay_snack_bar.dart` # adapts from Flutter v3.3.3 `SnackBar` in `/material/snack_bar.dart` # From 8513da9c15219e17100d4f9ee8e0c0995988c6ea Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Fri, 17 Mar 2023 19:54:16 +0100 Subject: [PATCH 10/11] fixed search when using the query bar --- CHANGELOG.md | 1 + lib/widgets/search/search_delegate.dart | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f97ffd6a..a189cbc36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file. ### Fixed - Viewer: playing video from app content provider +- Search: using the query bar yields a black screen ## [v1.8.3] - 2023-03-13 diff --git a/lib/widgets/search/search_delegate.dart b/lib/widgets/search/search_delegate.dart index 7dba129a5..d982aeeaf 100644 --- a/lib/widgets/search/search_delegate.dart +++ b/lib/widgets/search/search_delegate.dart @@ -272,14 +272,20 @@ class CollectionSearchDelegate extends AvesSearchDelegate with FeedbackMixin, Va ); } + var _selectingFromQuery = false; + @override Widget buildResults(BuildContext context) { - WidgetsBinding.instance.addPostFrameCallback((_) { - // `buildResults` is called in the build phase, - // so we post the call that will filter the collection - // and possibly trigger a rebuild here - _select(context, _buildQueryFilter(true)); - }); + // guard against multiple build calls + if (!_selectingFromQuery) { + _selectingFromQuery = true; + WidgetsBinding.instance.addPostFrameCallback((_) { + // `buildResults` is called in the build phase, + // so we post the call that will filter the collection + // and possibly trigger a rebuild here + _select(context, _buildQueryFilter(true)); + }); + } return const SizedBox(); } From 18e120a6d33e24a85b60f798d0c2f62403ef72c2 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Fri, 17 Mar 2023 19:57:45 +0100 Subject: [PATCH 11/11] version bump --- CHANGELOG.md | 2 ++ fastlane/metadata/android/en-US/changelogs/95.txt | 5 +++++ fastlane/metadata/android/en-US/changelogs/9501.txt | 5 +++++ pubspec.yaml | 2 +- whatsnew/whatsnew-en-US | 2 +- 5 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/95.txt create mode 100644 fastlane/metadata/android/en-US/changelogs/9501.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index a189cbc36..66a4b977c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [v1.8.4] - 2023-03-17 + ### Added - TV: improved support for Licenses diff --git a/fastlane/metadata/android/en-US/changelogs/95.txt b/fastlane/metadata/android/en-US/changelogs/95.txt new file mode 100644 index 000000000..3ee89824b --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/95.txt @@ -0,0 +1,5 @@ +In v1.8.4: +- view items in full-screen when selecting them +- watch videos using picture-in-picture +- navigate with TalkBack +Full changelog available on GitHub \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/9501.txt b/fastlane/metadata/android/en-US/changelogs/9501.txt new file mode 100644 index 000000000..3ee89824b --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/9501.txt @@ -0,0 +1,5 @@ +In v1.8.4: +- view items in full-screen when selecting them +- watch videos using picture-in-picture +- navigate with TalkBack +Full changelog available on GitHub \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 22289c7d3..e34ee4d0c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,7 +7,7 @@ repository: https://github.com/deckerst/aves # - play changelog: /whatsnew/whatsnew-en-US # - izzy changelog: /fastlane/metadata/android/en-US/changelogs/XX01.txt # - libre changelog: /fastlane/metadata/android/en-US/changelogs/XX.txt -version: 1.8.3+94 +version: 1.8.4+95 publish_to: none environment: diff --git a/whatsnew/whatsnew-en-US b/whatsnew/whatsnew-en-US index 7615d4920..3ee89824b 100644 --- a/whatsnew/whatsnew-en-US +++ b/whatsnew/whatsnew-en-US @@ -1,4 +1,4 @@ -In v1.8.3: +In v1.8.4: - view items in full-screen when selecting them - watch videos using picture-in-picture - navigate with TalkBack