refactored drawer

This commit is contained in:
Thibault Deckers 2020-09-03 20:35:07 +09:00
parent 80644f036b
commit ad397f0afc
7 changed files with 343 additions and 331 deletions

View file

@ -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';

View file

@ -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<AppDrawer> {
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 = <Widget>[
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<MediaQueryData, double>(
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<AlbumsChangedEvent>(),
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<AlbumsChangedEvent>(),
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<LocationsChangedEvent>(),
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<TagsChangedEvent>(),
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,
);
}
}

View file

@ -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<AppDrawer> {
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 = <Widget>[
header,
allCollectionTile,
videoTile,
favouriteTile,
_buildSpecialAlbumSection(),
Divider(),
albumListTile,
countryListTile,
tagListTile,
Divider(),
settingsTile,
aboutTile,
if (kDebugMode) ...[
Divider(),
debugTile,
],
];
return Drawer(
child: Selector<MediaQueryData, double>(
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<AlbumsChangedEvent>(),
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<AlbumsChangedEvent>(),
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<LocationsChangedEvent>(),
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<TagsChangedEvent>(),
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),
);
}

View file

@ -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,
);
}
}

View file

@ -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,
),
);
}
}

View file

@ -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';

View file

@ -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(