diff --git a/lib/widgets/album/collection_page.dart b/lib/widgets/album/collection_page.dart index 05a2dafbf..8be4a541e 100644 --- a/lib/widgets/album/collection_page.dart +++ b/lib/widgets/album/collection_page.dart @@ -1,8 +1,8 @@ import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/widgets/album/thumbnail_collection.dart'; -import 'package:aves/widgets/app_drawer.dart'; import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart'; import 'package:aves/widgets/common/double_back_pop.dart'; +import 'package:aves/widgets/drawer/app_drawer.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; diff --git a/lib/widgets/app_drawer.dart b/lib/widgets/app_drawer.dart deleted file mode 100644 index 47ccdccc5..000000000 --- a/lib/widgets/app_drawer.dart +++ /dev/null @@ -1,328 +0,0 @@ -import 'dart:ui'; - -import 'package:aves/model/filters/album.dart'; -import 'package:aves/model/filters/favourite.dart'; -import 'package:aves/model/filters/filters.dart'; -import 'package:aves/model/filters/mime.dart'; -import 'package:aves/model/mime_types.dart'; -import 'package:aves/model/settings.dart'; -import 'package:aves/model/source/album.dart'; -import 'package:aves/model/source/collection_lens.dart'; -import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/model/source/location.dart'; -import 'package:aves/model/source/tag.dart'; -import 'package:aves/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'; -import 'package:aves/widgets/common/icons.dart'; -import 'package:aves/widgets/debug_page.dart'; -import 'package:aves/widgets/filter_grids/albums_page.dart'; -import 'package:aves/widgets/filter_grids/countries_page.dart'; -import 'package:aves/widgets/filter_grids/tags_page.dart'; -import 'package:aves/widgets/settings/settings_page.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -class AppDrawer extends StatefulWidget { - final CollectionSource source; - - const AppDrawer({@required this.source}); - - @override - _AppDrawerState createState() => _AppDrawerState(); -} - -class _AppDrawerState extends State { - CollectionSource get source => widget.source; - - @override - Widget build(BuildContext context) { - final header = Container( - decoration: BoxDecoration( - border: Border( - bottom: Divider.createBorderSide(context), - ), - ), - child: Container( - padding: EdgeInsets.all(16), - color: Theme.of(context).accentColor, - child: SafeArea( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Align( - alignment: AlignmentDirectional.centerStart, - child: Wrap( - spacing: 16, - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - AvesLogo(size: 64), - Text( - 'Aves', - style: TextStyle( - fontSize: 44, - fontFamily: 'Concourse Caps', - ), - ), - ], - ), - ), - ], - ), - ), - ), - ); - - final allCollectionEntry = _FilteredCollectionNavTile( - source: source, - leading: Icon(AIcons.allCollection), - title: 'All collection', - filter: null, - ); - final videoEntry = _FilteredCollectionNavTile( - source: source, - leading: Icon(AIcons.video), - title: 'Videos', - filter: MimeFilter(MimeTypes.anyVideo), - ); - final favouriteEntry = _FilteredCollectionNavTile( - source: source, - leading: Icon(AIcons.favourite), - title: 'Favourites', - filter: FavouriteFilter(), - ); - final settingsEntry = SafeArea( - top: false, - bottom: false, - child: ListTile( - leading: Icon(AIcons.settings), - title: Text('Preferences'), - onTap: () => _goTo(SettingsPage.routeName, (_) => SettingsPage()), - ), - ); - final aboutEntry = SafeArea( - top: false, - bottom: false, - child: ListTile( - leading: Icon(AIcons.info), - title: Text('About'), - onTap: () => _goTo(AboutPage.routeName, (_) => AboutPage()), - ), - ); - - final drawerItems = [ - header, - allCollectionEntry, - videoEntry, - favouriteEntry, - _buildSpecialAlbumSection(), - Divider(), - _buildAlbumListEntry(), - _buildCountryListEntry(), - _buildTagListEntry(), - Divider(), - settingsEntry, - aboutEntry, - if (kDebugMode) ...[ - Divider(), - SafeArea( - top: false, - bottom: false, - child: ListTile( - leading: Icon(AIcons.debug), - title: Text('Debug'), - onTap: () => _goTo(DebugPage.routeName, (_) => DebugPage(source: source)), - ), - ), - ], - ]; - - return Drawer( - child: Selector( - selector: (c, mq) => mq.viewInsets.bottom, - builder: (c, mqViewInsetsBottom, child) { - return SingleChildScrollView( - padding: EdgeInsets.only(bottom: mqViewInsetsBottom), - child: Theme( - data: Theme.of(context).copyWith( - // color used by `ExpansionTile` for leading icon - unselectedWidgetColor: Colors.white, - ), - child: Column( - children: drawerItems, - ), - ), - ); - }, - ), - ); - } - - Widget _buildAlbumEntry(String album, {bool dense = true}) { - final uniqueName = source.getUniqueAlbumName(album); - return _FilteredCollectionNavTile( - source: source, - leading: IconUtils.getAlbumIcon(context: context, album: album), - title: uniqueName, - trailing: androidFileUtils.isOnRemovableStorage(album) - ? Icon( - AIcons.removableStorage, - size: 16, - color: Colors.grey, - ) - : null, - dense: dense, - filter: AlbumFilter(album, uniqueName), - ); - } - - Widget _buildSpecialAlbumSection() { - return StreamBuilder( - stream: source.eventBus.on(), - builder: (context, snapshot) { - final specialAlbums = source.sortedAlbums.where((album) { - final type = androidFileUtils.getAlbumType(album); - return [AlbumType.camera, AlbumType.screenshots].contains(type); - }); - - if (specialAlbums.isEmpty) return SizedBox.shrink(); - return Column( - children: [ - Divider(), - ...specialAlbums.map((album) => _buildAlbumEntry(album, dense: false)), - ], - ); - }); - } - - Widget _buildAlbumListEntry() { - return SafeArea( - top: false, - bottom: false, - child: ListTile( - key: Key('albums-tile'), - leading: Icon(AIcons.album), - title: Text('Albums'), - trailing: StreamBuilder( - stream: source.eventBus.on(), - builder: (context, snapshot) { - return Text( - '${source.sortedAlbums.length}', - style: TextStyle( - color: Colors.white.withOpacity(.6), - ), - ); - }), - onTap: () => _goTo(AlbumListPage.routeName, (_) => AlbumListPage(source: source)), - ), - ); - } - - Widget _buildCountryListEntry() { - return SafeArea( - top: false, - bottom: false, - child: ListTile( - leading: Icon(AIcons.location), - title: Text('Countries'), - trailing: StreamBuilder( - stream: source.eventBus.on(), - builder: (context, snapshot) { - return Text( - '${source.sortedCountries.length}', - style: TextStyle( - color: Colors.white.withOpacity(.6), - ), - ); - }), - onTap: () => _goTo(CountryListPage.routeName, (_) => CountryListPage(source: source)), - ), - ); - } - - Widget _buildTagListEntry() { - return SafeArea( - top: false, - bottom: false, - child: ListTile( - leading: Icon(AIcons.tag), - title: Text('Tags'), - trailing: StreamBuilder( - stream: source.eventBus.on(), - builder: (context, snapshot) { - return Text( - '${source.sortedTags.length}', - style: TextStyle( - color: Colors.white.withOpacity(.6), - ), - ); - }), - onTap: () => _goTo(TagListPage.routeName, (_) => TagListPage(source: source)), - ), - ); - } - - void _goTo(String routeName, WidgetBuilder builder) { - Navigator.pop(context); - if (routeName != context.currentRouteName) { - Navigator.push( - context, - MaterialPageRoute( - settings: RouteSettings(name: routeName), - builder: builder, - )); - } - } -} - -class _FilteredCollectionNavTile extends StatelessWidget { - final CollectionSource source; - final Widget leading; - final String title; - final Widget trailing; - final bool dense; - final CollectionFilter filter; - - const _FilteredCollectionNavTile({ - @required this.source, - @required this.leading, - @required this.title, - this.trailing, - bool dense, - @required this.filter, - }) : dense = dense ?? false; - - @override - Widget build(BuildContext context) { - return SafeArea( - top: false, - bottom: false, - child: ListTile( - leading: leading, - title: Text(title), - trailing: trailing, - dense: dense, - onTap: () => _goToCollection(context), - ), - ); - } - - void _goToCollection(BuildContext context) { - Navigator.pushAndRemoveUntil( - context, - MaterialPageRoute( - settings: RouteSettings(name: CollectionPage.routeName), - builder: (context) => CollectionPage(CollectionLens( - source: source, - filters: [filter], - groupFactor: settings.collectionGroupFactor, - sortFactor: settings.collectionSortFactor, - )), - ), - (route) => false, - ); - } -} diff --git a/lib/widgets/drawer/app_drawer.dart b/lib/widgets/drawer/app_drawer.dart new file mode 100644 index 000000000..54eb6d4fd --- /dev/null +++ b/lib/widgets/drawer/app_drawer.dart @@ -0,0 +1,228 @@ +import 'dart:ui'; + +import 'package:aves/model/filters/album.dart'; +import 'package:aves/model/filters/favourite.dart'; +import 'package:aves/model/filters/mime.dart'; +import 'package:aves/model/mime_types.dart'; +import 'package:aves/model/source/album.dart'; +import 'package:aves/model/source/collection_source.dart'; +import 'package:aves/model/source/location.dart'; +import 'package:aves/model/source/tag.dart'; +import 'package:aves/utils/android_file_utils.dart'; +import 'package:aves/widgets/about/about_page.dart'; +import 'package:aves/widgets/common/aves_logo.dart'; +import 'package:aves/widgets/common/icons.dart'; +import 'package:aves/widgets/debug_page.dart'; +import 'package:aves/widgets/drawer/collection_tile.dart'; +import 'package:aves/widgets/drawer/tile.dart'; +import 'package:aves/widgets/filter_grids/albums_page.dart'; +import 'package:aves/widgets/filter_grids/countries_page.dart'; +import 'package:aves/widgets/filter_grids/tags_page.dart'; +import 'package:aves/widgets/settings/settings_page.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class AppDrawer extends StatefulWidget { + final CollectionSource source; + + const AppDrawer({@required this.source}); + + @override + _AppDrawerState createState() => _AppDrawerState(); +} + +class _AppDrawerState extends State { + CollectionSource get source => widget.source; + + @override + Widget build(BuildContext context) { + final header = Container( + decoration: BoxDecoration( + border: Border( + bottom: Divider.createBorderSide(context), + ), + ), + child: Container( + padding: EdgeInsets.all(16), + color: Theme.of(context).accentColor, + child: SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Align( + alignment: AlignmentDirectional.centerStart, + child: Wrap( + spacing: 16, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + AvesLogo(size: 64), + Text( + 'Aves', + style: TextStyle( + fontSize: 44, + fontFamily: 'Concourse Caps', + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + + final drawerItems = [ + header, + allCollectionTile, + videoTile, + favouriteTile, + _buildSpecialAlbumSection(), + Divider(), + albumListTile, + countryListTile, + tagListTile, + Divider(), + settingsTile, + aboutTile, + if (kDebugMode) ...[ + Divider(), + debugTile, + ], + ]; + + return Drawer( + child: Selector( + selector: (c, mq) => mq.viewInsets.bottom, + builder: (c, mqViewInsetsBottom, child) { + return SingleChildScrollView( + padding: EdgeInsets.only(bottom: mqViewInsetsBottom), + child: Theme( + data: Theme.of(context).copyWith( + // color used by `ExpansionTile` for leading icon + unselectedWidgetColor: Colors.white, + ), + child: Column( + children: drawerItems, + ), + ), + ); + }, + ), + ); + } + + Widget _buildAlbumTile(String album) { + final uniqueName = source.getUniqueAlbumName(album); + return CollectionNavTile( + source: source, + leading: IconUtils.getAlbumIcon(context: context, album: album), + title: uniqueName, + trailing: androidFileUtils.isOnRemovableStorage(album) + ? Icon( + AIcons.removableStorage, + size: 16, + color: Colors.grey, + ) + : null, + filter: AlbumFilter(album, uniqueName), + ); + } + + Widget _buildSpecialAlbumSection() { + return StreamBuilder( + stream: source.eventBus.on(), + builder: (context, snapshot) { + final specialAlbums = source.sortedAlbums.where((album) { + final type = androidFileUtils.getAlbumType(album); + return [AlbumType.camera, AlbumType.screenshots].contains(type); + }); + + if (specialAlbums.isEmpty) return SizedBox.shrink(); + return Column( + children: [ + Divider(), + ...specialAlbums.map(_buildAlbumTile), + ], + ); + }); + } + + // tiles + + Widget get allCollectionTile => CollectionNavTile( + source: source, + leading: Icon(AIcons.allCollection), + title: 'All collection', + filter: null, + ); + + Widget get videoTile => CollectionNavTile( + source: source, + leading: Icon(AIcons.video), + title: 'Videos', + filter: MimeFilter(MimeTypes.anyVideo), + ); + + Widget get favouriteTile => CollectionNavTile( + source: source, + leading: Icon(AIcons.favourite), + title: 'Favourites', + filter: FavouriteFilter(), + ); + + Widget get albumListTile => NavTile( + icon: AIcons.album, + title: 'Albums', + trailing: StreamBuilder( + stream: source.eventBus.on(), + builder: (context, _) => Text('${source.sortedAlbums.length}'), + ), + routeName: AlbumListPage.routeName, + pageBuilder: (_) => AlbumListPage(source: source), + ); + + Widget get countryListTile => NavTile( + icon: AIcons.location, + title: 'Countries', + trailing: StreamBuilder( + stream: source.eventBus.on(), + builder: (context, _) => Text('${source.sortedCountries.length}'), + ), + routeName: CountryListPage.routeName, + pageBuilder: (_) => CountryListPage(source: source), + ); + + Widget get tagListTile => NavTile( + icon: AIcons.tag, + title: 'Tags', + trailing: StreamBuilder( + stream: source.eventBus.on(), + builder: (context, _) => Text('${source.sortedTags.length}'), + ), + routeName: TagListPage.routeName, + pageBuilder: (_) => TagListPage(source: source), + ); + + Widget get settingsTile => NavTile( + icon: AIcons.settings, + title: 'Preferences', + routeName: SettingsPage.routeName, + pageBuilder: (_) => SettingsPage(), + ); + + Widget get aboutTile => NavTile( + icon: AIcons.info, + title: 'About', + routeName: AboutPage.routeName, + pageBuilder: (_) => AboutPage(), + ); + + Widget get debugTile => NavTile( + icon: AIcons.debug, + title: 'Debug', + routeName: DebugPage.routeName, + pageBuilder: (_) => DebugPage(source: source), + ); +} diff --git a/lib/widgets/drawer/collection_tile.dart b/lib/widgets/drawer/collection_tile.dart new file mode 100644 index 000000000..cd9d739cb --- /dev/null +++ b/lib/widgets/drawer/collection_tile.dart @@ -0,0 +1,56 @@ +import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/settings.dart'; +import 'package:aves/model/source/collection_lens.dart'; +import 'package:aves/model/source/collection_source.dart'; +import 'package:aves/widgets/album/collection_page.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +class CollectionNavTile extends StatelessWidget { + final CollectionSource source; + final Widget leading; + final String title; + final Widget trailing; + final bool dense; + final CollectionFilter filter; + + const CollectionNavTile({ + @required this.source, + @required this.leading, + @required this.title, + this.trailing, + bool dense, + @required this.filter, + }) : dense = dense ?? false; + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + bottom: false, + child: ListTile( + leading: leading, + title: Text(title), + trailing: trailing, + dense: dense, + onTap: () => _goToCollection(context), + ), + ); + } + + void _goToCollection(BuildContext context) { + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute( + settings: RouteSettings(name: CollectionPage.routeName), + builder: (context) => CollectionPage(CollectionLens( + source: source, + filters: [filter], + groupFactor: settings.collectionGroupFactor, + sortFactor: settings.collectionSortFactor, + )), + ), + (route) => false, + ); + } +} diff --git a/lib/widgets/drawer/tile.dart b/lib/widgets/drawer/tile.dart new file mode 100644 index 000000000..4b51362bd --- /dev/null +++ b/lib/widgets/drawer/tile.dart @@ -0,0 +1,56 @@ +import 'dart:ui'; + +import 'package:aves/utils/flutter_utils.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +class NavTile extends StatelessWidget { + final IconData icon; + final String title; + final Widget trailing; + final String routeName; + final WidgetBuilder pageBuilder; + + const NavTile({ + @required this.icon, + @required this.title, + this.trailing, + @required this.routeName, + @required this.pageBuilder, + }); + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + bottom: false, + child: ListTile( + key: Key('$title-tile'), + leading: Icon(icon), + title: Text(title), + trailing: trailing != null + ? Builder( + builder: (context) => DefaultTextStyle.merge( + style: TextStyle( + color: IconTheme.of(context).color.withOpacity(.6), + ), + child: trailing, + ), + ) + : null, + onTap: () { + Navigator.pop(context); + if (routeName != context.currentRouteName) { + Navigator.push( + context, + MaterialPageRoute( + settings: RouteSettings(name: routeName), + builder: pageBuilder, + )); + } + }, + selected: context.currentRouteName == routeName, + ), + ); + } +} diff --git a/lib/widgets/filter_grids/filter_grid_page.dart b/lib/widgets/filter_grids/filter_grid_page.dart index 72663f830..c526562dc 100644 --- a/lib/widgets/filter_grids/filter_grid_page.dart +++ b/lib/widgets/filter_grids/filter_grid_page.dart @@ -7,10 +7,10 @@ import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/utils/durations.dart'; import 'package:aves/widgets/album/collection_page.dart'; -import 'package:aves/widgets/app_drawer.dart'; import 'package:aves/widgets/common/app_bar_subtitle.dart'; import 'package:aves/widgets/common/aves_filter_chip.dart'; import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart'; +import 'package:aves/widgets/drawer/app_drawer.dart'; import 'package:aves/widgets/filter_grids/decorated_filter_chip.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; diff --git a/test_driver/app_test.dart b/test_driver/app_test.dart index c826681b1..d096e431a 100644 --- a/test_driver/app_test.dart +++ b/test_driver/app_test.dart @@ -91,7 +91,7 @@ void selectFirstAlbum() { await driver.tap(find.byValueKey('appbar-leading-button')); await driver.waitUntilNoTransientCallbacks(); - await driver.tap(find.byValueKey('albums-tile')); + await driver.tap(find.byValueKey('Albums-tile')); await driver.waitUntilNoTransientCallbacks(); await driver.tap(find.descendant(