diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 5510adbf4..4adf69e21 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -5,6 +5,8 @@ on: branches: - develop +# TODO TLAD run `flutter format -l 1000 .` and fail if any + jobs: build: name: Check code quality. diff --git a/lib/model/settings.dart b/lib/model/settings.dart index 97b2116e2..d5c644a51 100644 --- a/lib/model/settings.dart +++ b/lib/model/settings.dart @@ -155,7 +155,7 @@ extension ExtraHomePageSetting on HomePageSetting { String get name { switch (this) { case HomePageSetting.collection: - return 'All Media'; + return 'Collection'; case HomePageSetting.albums: return 'Albums'; default: diff --git a/lib/utils/flutter_utils.dart b/lib/utils/flutter_utils.dart new file mode 100644 index 000000000..3f06767b8 --- /dev/null +++ b/lib/utils/flutter_utils.dart @@ -0,0 +1,5 @@ +import 'package:flutter/widgets.dart'; + +extension ExtraContext on BuildContext { + String get currentRouteName => ModalRoute.of(this)?.settings?.name; +} diff --git a/lib/widgets/about/about_page.dart b/lib/widgets/about/about_page.dart index 8ac977f39..6e35112f6 100644 --- a/lib/widgets/about/about_page.dart +++ b/lib/widgets/about/about_page.dart @@ -7,6 +7,8 @@ import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:package_info/package_info.dart'; class AboutPage extends StatelessWidget { + static const routeName = '/about'; + @override Widget build(BuildContext context) { return Scaffold( diff --git a/lib/widgets/album/app_bar.dart b/lib/widgets/album/app_bar.dart index bb7d9fade..f55698bef 100644 --- a/lib/widgets/album/app_bar.dart +++ b/lib/widgets/album/app_bar.dart @@ -134,7 +134,10 @@ class _CollectionAppBarState extends State with SingleTickerPr Widget _buildAppBarTitle() { if (collection.isBrowsing) { - Widget title = Text(AvesApp.mode == AppMode.pick ? 'Select' : 'Aves', key: Key('appbar-title')); + Widget title = Text( + AvesApp.mode == AppMode.pick ? 'Select' : 'Collection', + key: Key('appbar-title'), + ); if (AvesApp.mode == AppMode.main) { title = SourceStateAwareAppBarTitle( title: title, @@ -345,6 +348,7 @@ class _CollectionAppBarState extends State with SingleTickerPr return Navigator.push( context, MaterialPageRoute( + settings: RouteSettings(name: StatsPage.routeName), builder: (context) => StatsPage( collection: collection, ), diff --git a/lib/widgets/album/collection_page.dart b/lib/widgets/album/collection_page.dart index 7349dc8ef..05a2dafbf 100644 --- a/lib/widgets/album/collection_page.dart +++ b/lib/widgets/album/collection_page.dart @@ -8,6 +8,8 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; class CollectionPage extends StatelessWidget { + static const routeName = '/collection'; + final CollectionLens collection; const CollectionPage(this.collection); diff --git a/lib/widgets/album/grid/list_sliver.dart b/lib/widgets/album/grid/list_sliver.dart index dc8845a33..9e4006779 100644 --- a/lib/widgets/album/grid/list_sliver.dart +++ b/lib/widgets/album/grid/list_sliver.dart @@ -5,7 +5,7 @@ import 'package:aves/services/viewer_service.dart'; import 'package:aves/widgets/album/grid/list_known_extent.dart'; import 'package:aves/widgets/album/grid/list_section_layout.dart'; import 'package:aves/widgets/album/thumbnail/decorated.dart'; -import 'package:aves/widgets/common/transparent_material_page_route.dart'; +import 'package:aves/widgets/common/routes.dart'; import 'package:aves/widgets/fullscreen/fullscreen_page.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -85,6 +85,7 @@ class GridThumbnail extends StatelessWidget { Navigator.push( context, TransparentMaterialPageRoute( + settings: RouteSettings(name: MultiFullscreenPage.routeName), pageBuilder: (c, a, sa) => MultiFullscreenPage( collection: collection, initialEntry: entry, diff --git a/lib/widgets/app_drawer.dart b/lib/widgets/app_drawer.dart index 0de8f56a2..47ccdccc5 100644 --- a/lib/widgets/app_drawer.dart +++ b/lib/widgets/app_drawer.dart @@ -12,6 +12,7 @@ import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/location.dart'; import 'package:aves/model/source/tag.dart'; import 'package:aves/utils/android_file_utils.dart'; +import 'package:aves/utils/flutter_utils.dart'; import 'package:aves/widgets/about/about_page.dart'; import 'package:aves/widgets/album/collection_page.dart'; import 'package:aves/widgets/common/aves_logo.dart'; @@ -75,10 +76,10 @@ class _AppDrawerState extends State { ), ); - final allMediaEntry = _FilteredCollectionNavTile( + final allCollectionEntry = _FilteredCollectionNavTile( source: source, - leading: Icon(AIcons.allMedia), - title: 'All media', + leading: Icon(AIcons.allCollection), + title: 'All collection', filter: null, ); final videoEntry = _FilteredCollectionNavTile( @@ -99,7 +100,7 @@ class _AppDrawerState extends State { child: ListTile( leading: Icon(AIcons.settings), title: Text('Preferences'), - onTap: () => _goTo((_) => SettingsPage()), + onTap: () => _goTo(SettingsPage.routeName, (_) => SettingsPage()), ), ); final aboutEntry = SafeArea( @@ -108,20 +109,20 @@ class _AppDrawerState extends State { child: ListTile( leading: Icon(AIcons.info), title: Text('About'), - onTap: () => _goTo((_) => AboutPage()), + onTap: () => _goTo(AboutPage.routeName, (_) => AboutPage()), ), ); final drawerItems = [ header, - allMediaEntry, + allCollectionEntry, videoEntry, favouriteEntry, _buildSpecialAlbumSection(), Divider(), - _buildRegularAlbumSection(), - _buildCountrySection(), - _buildTagSection(), + _buildAlbumListEntry(), + _buildCountryListEntry(), + _buildTagListEntry(), Divider(), settingsEntry, aboutEntry, @@ -133,7 +134,7 @@ class _AppDrawerState extends State { child: ListTile( leading: Icon(AIcons.debug), title: Text('Debug'), - onTap: () => _goTo((_) => DebugPage(source: source)), + onTap: () => _goTo(DebugPage.routeName, (_) => DebugPage(source: source)), ), ), ], @@ -184,7 +185,7 @@ class _AppDrawerState extends State { builder: (context, snapshot) { final specialAlbums = source.sortedAlbums.where((album) { final type = androidFileUtils.getAlbumType(album); - return type != AlbumType.regular && type != AlbumType.app; + return [AlbumType.camera, AlbumType.screenshots].contains(type); }); if (specialAlbums.isEmpty) return SizedBox.shrink(); @@ -197,7 +198,7 @@ class _AppDrawerState extends State { }); } - Widget _buildRegularAlbumSection() { + Widget _buildAlbumListEntry() { return SafeArea( top: false, bottom: false, @@ -215,12 +216,12 @@ class _AppDrawerState extends State { ), ); }), - onTap: () => _goTo((_) => AlbumListPage(source: source)), + onTap: () => _goTo(AlbumListPage.routeName, (_) => AlbumListPage(source: source)), ), ); } - Widget _buildCountrySection() { + Widget _buildCountryListEntry() { return SafeArea( top: false, bottom: false, @@ -237,12 +238,12 @@ class _AppDrawerState extends State { ), ); }), - onTap: () => _goTo((_) => CountryListPage(source: source)), + onTap: () => _goTo(CountryListPage.routeName, (_) => CountryListPage(source: source)), ), ); } - Widget _buildTagSection() { + Widget _buildTagListEntry() { return SafeArea( top: false, bottom: false, @@ -259,14 +260,21 @@ class _AppDrawerState extends State { ), ); }), - onTap: () => _goTo((_) => TagListPage(source: source)), + onTap: () => _goTo(TagListPage.routeName, (_) => TagListPage(source: source)), ), ); } - void _goTo(WidgetBuilder builder) { + void _goTo(String routeName, WidgetBuilder builder) { Navigator.pop(context); - Navigator.push(context, MaterialPageRoute(builder: builder)); + if (routeName != context.currentRouteName) { + Navigator.push( + context, + MaterialPageRoute( + settings: RouteSettings(name: routeName), + builder: builder, + )); + } } } @@ -306,6 +314,7 @@ class _FilteredCollectionNavTile extends StatelessWidget { Navigator.pushAndRemoveUntil( context, MaterialPageRoute( + settings: RouteSettings(name: CollectionPage.routeName), builder: (context) => CollectionPage(CollectionLens( source: source, filters: [filter], diff --git a/lib/widgets/common/action_delegates/entry_action_delegate.dart b/lib/widgets/common/action_delegates/entry_action_delegate.dart index 368acd591..4aac59c72 100644 --- a/lib/widgets/common/action_delegates/entry_action_delegate.dart +++ b/lib/widgets/common/action_delegates/entry_action_delegate.dart @@ -174,6 +174,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin { Navigator.push( context, MaterialPageRoute( + settings: RouteSettings(name: FullscreenDebugPage.routeName), builder: (context) => FullscreenDebugPage(entry: entry), ), ); diff --git a/lib/widgets/common/icons.dart b/lib/widgets/common/icons.dart index fba2069dd..42d2f9f2c 100644 --- a/lib/widgets/common/icons.dart +++ b/lib/widgets/common/icons.dart @@ -7,7 +7,7 @@ import 'package:flutter/material.dart'; import 'package:outline_material_icons/outline_material_icons.dart'; class AIcons { - static const IconData allMedia = OMIcons.collections; + static const IconData allCollection = OMIcons.collections; static const IconData image = OMIcons.photo; static const IconData video = OMIcons.movie; static const IconData vector = OMIcons.code; diff --git a/lib/widgets/common/routes.dart b/lib/widgets/common/routes.dart new file mode 100644 index 000000000..a5ac9d079 --- /dev/null +++ b/lib/widgets/common/routes.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; + +class DirectMaterialPageRoute extends PageRouteBuilder { + DirectMaterialPageRoute({ + RouteSettings settings, + @required WidgetBuilder builder, + }) : super( + settings: settings, + transitionDuration: Duration.zero, + pageBuilder: (c, a, sa) => builder(c), + ); + + @override + Widget buildTransitions(BuildContext context, Animation animation, Animation secondaryAnimation, Widget child) { + return child; + } +} + +class TransparentMaterialPageRoute extends PageRouteBuilder { + TransparentMaterialPageRoute({ + RouteSettings settings, + @required RoutePageBuilder pageBuilder, + }) : super(settings: settings, pageBuilder: pageBuilder); + + @override + bool get opaque => false; + + @override + Widget buildTransitions(BuildContext context, Animation animation, Animation secondaryAnimation, Widget child) { + final theme = Theme.of(context).pageTransitionsTheme; + return theme.buildTransitions(this, context, animation, secondaryAnimation, child); + } +} diff --git a/lib/widgets/common/transparent_material_page_route.dart b/lib/widgets/common/transparent_material_page_route.dart deleted file mode 100644 index 30f52855a..000000000 --- a/lib/widgets/common/transparent_material_page_route.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:flutter/material.dart'; - -class TransparentMaterialPageRoute extends PageRouteBuilder { - TransparentMaterialPageRoute({ - @required RoutePageBuilder pageBuilder, - }) : super(pageBuilder: pageBuilder); - - @override - bool get opaque => false; - - @override - Widget buildTransitions(BuildContext context, Animation animation, Animation secondaryAnimation, Widget child) { - final theme = Theme.of(context).pageTransitionsTheme; - return theme.buildTransitions(this, context, animation, secondaryAnimation, child); - } -} diff --git a/lib/widgets/debug_page.dart b/lib/widgets/debug_page.dart index bc046a965..e770d2cf8 100644 --- a/lib/widgets/debug_page.dart +++ b/lib/widgets/debug_page.dart @@ -18,6 +18,8 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:outline_material_icons/outline_material_icons.dart'; class DebugPage extends StatefulWidget { + static const routeName = '/debug'; + final CollectionSource source; const DebugPage({this.source}); diff --git a/lib/widgets/filter_grids/albums_page.dart b/lib/widgets/filter_grids/albums_page.dart index b696941e3..ec6b817b6 100644 --- a/lib/widgets/filter_grids/albums_page.dart +++ b/lib/widgets/filter_grids/albums_page.dart @@ -17,6 +17,8 @@ import 'package:flutter/scheduler.dart'; import 'package:provider/provider.dart'; class AlbumListPage extends StatelessWidget { + static const routeName = '/albums'; + final CollectionSource source; const AlbumListPage({@required this.source}); diff --git a/lib/widgets/filter_grids/countries_page.dart b/lib/widgets/filter_grids/countries_page.dart index 243babe8a..c9098f568 100644 --- a/lib/widgets/filter_grids/countries_page.dart +++ b/lib/widgets/filter_grids/countries_page.dart @@ -7,6 +7,8 @@ import 'package:aves/widgets/filter_grids/filter_grid_page.dart'; import 'package:flutter/material.dart'; class CountryListPage extends StatelessWidget { + static const routeName = '/countries'; + final CollectionSource source; const CountryListPage({@required this.source}); diff --git a/lib/widgets/filter_grids/filter_grid_page.dart b/lib/widgets/filter_grids/filter_grid_page.dart index 95ef0665a..72663f830 100644 --- a/lib/widgets/filter_grids/filter_grid_page.dart +++ b/lib/widgets/filter_grids/filter_grid_page.dart @@ -56,6 +56,7 @@ class FilterNavigationPage extends StatelessWidget { onPressed: (filter) => Navigator.pushAndRemoveUntil( context, MaterialPageRoute( + settings: RouteSettings(name: CollectionPage.routeName), builder: (context) => CollectionPage(CollectionLens( source: source, filters: [filter], diff --git a/lib/widgets/filter_grids/tags_page.dart b/lib/widgets/filter_grids/tags_page.dart index 27fee464c..4b4693f89 100644 --- a/lib/widgets/filter_grids/tags_page.dart +++ b/lib/widgets/filter_grids/tags_page.dart @@ -7,6 +7,8 @@ import 'package:aves/widgets/filter_grids/filter_grid_page.dart'; import 'package:flutter/material.dart'; class TagListPage extends StatelessWidget { + static const routeName = '/tags'; + final CollectionSource source; const TagListPage({@required this.source}); diff --git a/lib/widgets/fullscreen/debug.dart b/lib/widgets/fullscreen/debug.dart index 173e773e6..713062394 100644 --- a/lib/widgets/fullscreen/debug.dart +++ b/lib/widgets/fullscreen/debug.dart @@ -10,6 +10,8 @@ import 'package:flutter/material.dart'; import 'package:tuple/tuple.dart'; class FullscreenDebugPage extends StatefulWidget { + static const routeName = '/fullscreen/debug'; + final ImageEntry entry; const FullscreenDebugPage({@required this.entry}); diff --git a/lib/widgets/fullscreen/fullscreen_body.dart b/lib/widgets/fullscreen/fullscreen_body.dart index 2e408e7e7..0e5113c0a 100644 --- a/lib/widgets/fullscreen/fullscreen_body.dart +++ b/lib/widgets/fullscreen/fullscreen_body.dart @@ -275,6 +275,7 @@ class FullscreenBodyState extends State with SingleTickerProvide Navigator.pushAndRemoveUntil( context, MaterialPageRoute( + settings: RouteSettings(name: CollectionPage.routeName), builder: (context) => CollectionPage(collection.derive(filter)), ), (route) => false, @@ -323,7 +324,7 @@ class FullscreenBodyState extends State with SingleTickerProvide } void _onLeave() { - if (!ModalRoute.of(context).canPop) { + if (!Navigator.canPop(context)) { // exit app when trying to pop a fullscreen page that is a viewer for a single entry exit(0); } diff --git a/lib/widgets/fullscreen/fullscreen_page.dart b/lib/widgets/fullscreen/fullscreen_page.dart index 32ddf1507..142bfd344 100644 --- a/lib/widgets/fullscreen/fullscreen_page.dart +++ b/lib/widgets/fullscreen/fullscreen_page.dart @@ -5,6 +5,8 @@ import 'package:aves/widgets/fullscreen/fullscreen_body.dart'; import 'package:flutter/material.dart'; class MultiFullscreenPage extends AnimatedWidget { + static const routeName = '/fullscreen'; + final CollectionLens collection; final ImageEntry initialEntry; @@ -30,6 +32,8 @@ class MultiFullscreenPage extends AnimatedWidget { } class SingleFullscreenPage extends StatelessWidget { + static const routeName = '/fullscreen'; + final ImageEntry entry; const SingleFullscreenPage({ diff --git a/lib/widgets/fullscreen/overlay/top.dart b/lib/widgets/fullscreen/overlay/top.dart index e46490484..ab648cc10 100644 --- a/lib/widgets/fullscreen/overlay/top.dart +++ b/lib/widgets/fullscreen/overlay/top.dart @@ -126,7 +126,7 @@ class _TopOverlayRow extends StatelessWidget { children: [ OverlayButton( scale: scale, - child: ModalRoute.of(context)?.canPop ?? true ? BackButton() : CloseButton(), + child: Navigator.canPop(context) ? BackButton() : CloseButton(), ), Spacer(), ...quickActions.map(_buildOverlayButton), diff --git a/lib/widgets/home_page.dart b/lib/widgets/home_page.dart index 732aa78a0..f80382a47 100644 --- a/lib/widgets/home_page.dart +++ b/lib/widgets/home_page.dart @@ -7,7 +7,7 @@ import 'package:aves/services/viewer_service.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/widgets/album/collection_page.dart'; import 'package:aves/widgets/common/data_providers/media_store_collection_provider.dart'; -import 'package:aves/widgets/common/icons.dart'; +import 'package:aves/widgets/common/routes.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart'; import 'package:aves/widgets/fullscreen/fullscreen_page.dart'; import 'package:flutter/material.dart'; @@ -17,6 +17,8 @@ import 'package:permission_handler/permission_handler.dart'; import 'package:screen/screen.dart'; class HomePage extends StatefulWidget { + static const routeName = '/'; + const HomePage(); @override @@ -26,16 +28,18 @@ class HomePage extends StatefulWidget { class _HomePageState extends State { MediaStoreSource _mediaStore; ImageEntry _viewerEntry; - Future _appSetup; @override void initState() { super.initState(); - _appSetup = _setup(); + _setup(); imageCache.maximumSizeBytes = 512 * (1 << 20); Screen.keepOn(true); } + @override + Widget build(BuildContext context) => Scaffold(); + Future _setup() async { final permissions = await [ Permission.storage, @@ -80,6 +84,8 @@ class _HomePageState extends State { await _mediaStore.init(); unawaited(_mediaStore.refresh()); } + + unawaited(Navigator.pushReplacement(context, _getRedirectRoute())); } Future _initViewerEntry({@required String uri, @required String mimeType}) async { @@ -92,30 +98,36 @@ class _HomePageState extends State { return entry; } - @override - Widget build(BuildContext context) { - return FutureBuilder( - future: _appSetup, - builder: (context, snapshot) { - if (snapshot.hasError) return Icon(AIcons.error); - if (snapshot.connectionState != ConnectionState.done) return Scaffold(); - if (AvesApp.mode == AppMode.view) { - return SingleFullscreenPage(entry: _viewerEntry); + Route _getRedirectRoute() { + switch (AvesApp.mode) { + case AppMode.view: + return DirectMaterialPageRoute( + settings: RouteSettings(name: SingleFullscreenPage.routeName), + builder: (_) => SingleFullscreenPage(entry: _viewerEntry), + ); + case AppMode.main: + case AppMode.pick: + if (_mediaStore != null) { + switch (settings.homePage) { + case HomePageSetting.albums: + return DirectMaterialPageRoute( + settings: RouteSettings(name: AlbumListPage.routeName), + builder: (_) => AlbumListPage(source: _mediaStore), + ); + case HomePageSetting.collection: + return DirectMaterialPageRoute( + settings: RouteSettings(name: CollectionPage.routeName), + builder: (_) => CollectionPage( + CollectionLens( + source: _mediaStore, + groupFactor: settings.collectionGroupFactor, + sortFactor: settings.collectionSortFactor, + ), + ), + ); } - if (_mediaStore != null) { - switch (settings.homePage) { - case HomePageSetting.albums: - return AlbumListPage(source: _mediaStore); - break; - case HomePageSetting.collection: - return CollectionPage(CollectionLens( - source: _mediaStore, - groupFactor: settings.collectionGroupFactor, - sortFactor: settings.collectionSortFactor, - )); - } - } - return SizedBox.shrink(); - }); + } + } + return null; } } diff --git a/lib/widgets/settings/settings_page.dart b/lib/widgets/settings/settings_page.dart index 0866c6cae..4962b7ff0 100644 --- a/lib/widgets/settings/settings_page.dart +++ b/lib/widgets/settings/settings_page.dart @@ -7,6 +7,8 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; class SettingsPage extends StatelessWidget { + static const routeName = '/settings'; + @override Widget build(BuildContext context) { return MediaQueryDataProvider( diff --git a/lib/widgets/stats/filter_table.dart b/lib/widgets/stats/filter_table.dart index 1646c06ec..6db934778 100644 --- a/lib/widgets/stats/filter_table.dart +++ b/lib/widgets/stats/filter_table.dart @@ -90,6 +90,7 @@ class FilterTable extends StatelessWidget { Navigator.pushAndRemoveUntil( context, MaterialPageRoute( + settings: RouteSettings(name: CollectionPage.routeName), builder: (context) => CollectionPage(collection.derive(filter)), ), (route) => false, diff --git a/lib/widgets/stats/stats.dart b/lib/widgets/stats/stats.dart index df58d04ac..d4db446c5 100644 --- a/lib/widgets/stats/stats.dart +++ b/lib/widgets/stats/stats.dart @@ -20,6 +20,8 @@ import 'package:intl/intl.dart'; import 'package:percent_indicator/linear_percent_indicator.dart'; class StatsPage extends StatelessWidget { + static const routeName = '/collection/stats'; + final CollectionLens collection; final Map entryCountPerCountry = {}, entryCountPerPlace = {}, entryCountPerTag = {}; @@ -236,6 +238,7 @@ class StatsPage extends StatelessWidget { Navigator.pushAndRemoveUntil( context, MaterialPageRoute( + settings: RouteSettings(name: CollectionPage.routeName), builder: (context) => CollectionPage(collection.derive(filter)), ), (route) => false, diff --git a/lib/widgets/welcome_page.dart b/lib/widgets/welcome_page.dart index 04a853983..4dcdb23f9 100644 --- a/lib/widgets/welcome_page.dart +++ b/lib/widgets/welcome_page.dart @@ -107,6 +107,7 @@ class _WelcomePageState extends State { Navigator.pushAndRemoveUntil( context, MaterialPageRoute( + settings: RouteSettings(name: HomePage.routeName), builder: (context) => HomePage(), ), (route) => false, diff --git a/test_driver/app_test.dart b/test_driver/app_test.dart index cc1c6cb94..c826681b1 100644 --- a/test_driver/app_test.dart +++ b/test_driver/app_test.dart @@ -56,7 +56,7 @@ void agreeToTerms() { await driver.tap(find.byValueKey('continue-button')); await driver.waitUntilNoTransientCallbacks(); - expect(await driver.getText(find.byValueKey('appbar-title')), 'Aves'); + expect(await driver.getText(find.byValueKey('appbar-title')), 'Collection'); }); }