Merge branch 'develop'

This commit is contained in:
Thibault Deckers 2020-08-02 19:30:16 +09:00
commit 041565b34a
17 changed files with 271 additions and 143 deletions

View file

@ -85,6 +85,8 @@ class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSel
bool get showHeaders { bool get showHeaders {
if (sortFactor == SortFactor.size) return false; if (sortFactor == SortFactor.size) return false;
if (sortFactor == SortFactor.date && groupFactor == GroupFactor.none) return false;
final albumSections = sortFactor == SortFactor.name || (sortFactor == SortFactor.date && groupFactor == GroupFactor.album); final albumSections = sortFactor == SortFactor.name || (sortFactor == SortFactor.date && groupFactor == GroupFactor.album);
final filterByAlbum = filters.any((f) => f is AlbumFilter); final filterByAlbum = filters.any((f) => f is AlbumFilter);
if (albumSections && filterByAlbum) return false; if (albumSections && filterByAlbum) return false;
@ -160,6 +162,11 @@ class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSel
case GroupFactor.day: case GroupFactor.day:
sections = groupBy<ImageEntry, DateTime>(_filteredEntries, (entry) => entry.dayTaken); sections = groupBy<ImageEntry, DateTime>(_filteredEntries, (entry) => entry.dayTaken);
break; break;
case GroupFactor.none:
sections = Map.fromEntries([
MapEntry(null, _filteredEntries),
]);
break;
} }
break; break;
case SortFactor.size: case SortFactor.size:
@ -209,7 +216,7 @@ class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSel
enum SortFactor { date, size, name } enum SortFactor { date, size, name }
enum GroupFactor { album, month, day } enum GroupFactor { none, album, month, day }
enum Activity { browse, select } enum Activity { browse, select }

View file

@ -22,6 +22,14 @@ class Constants {
static const svgBackground = Colors.white; static const svgBackground = Colors.white;
static const svgColorFilter = ColorFilter.mode(svgBackground, BlendMode.dstOver); static const svgColorFilter = ColorFilter.mode(svgBackground, BlendMode.dstOver);
static const dialogContentHorizontalPadding = EdgeInsets.symmetric(horizontal: 24);
static const dialogActionsPadding = EdgeInsets.symmetric(horizontal: 8);
static const dialogShape = RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(24),
),
);
static const List<Dependency> androidDependencies = [ static const List<Dependency> androidDependencies = [
Dependency( Dependency(
name: 'CWAC-Document', name: 'CWAC-Document',

View file

@ -11,7 +11,7 @@ class Durations {
// collection animations // collection animations
static const appBarTitleAnimation = Duration(milliseconds: 300); static const appBarTitleAnimation = Duration(milliseconds: 300);
static const filterBarRemovalAnimation = Duration(milliseconds: 200); static const filterBarRemovalAnimation = Duration(milliseconds: 400);
static const collectionOpOverlayAnimation = Duration(milliseconds: 300); static const collectionOpOverlayAnimation = Duration(milliseconds: 300);
static const collectionScalingBackgroundAnimation = Duration(milliseconds: 200); static const collectionScalingBackgroundAnimation = Duration(milliseconds: 200);
static const sectionHeaderAnimation = Duration(milliseconds: 200); static const sectionHeaderAnimation = Duration(milliseconds: 200);

View file

@ -6,7 +6,9 @@ import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/utils/durations.dart'; import 'package:aves/utils/durations.dart';
import 'package:aves/widgets/album/filter_bar.dart'; import 'package:aves/widgets/album/filter_bar.dart';
import 'package:aves/widgets/album/search/search_delegate.dart'; import 'package:aves/widgets/album/search/search_delegate.dart';
import 'package:aves/widgets/common/action_delegates/group_collection_dialog.dart';
import 'package:aves/widgets/common/action_delegates/selection_action_delegate.dart'; import 'package:aves/widgets/common/action_delegates/selection_action_delegate.dart';
import 'package:aves/widgets/common/action_delegates/sort_collection_dialog.dart';
import 'package:aves/widgets/common/app_bar_subtitle.dart'; import 'package:aves/widgets/common/app_bar_subtitle.dart';
import 'package:aves/widgets/common/data_providers/media_store_collection_provider.dart'; import 'package:aves/widgets/common/data_providers/media_store_collection_provider.dart';
import 'package:aves/widgets/common/entry_actions.dart'; import 'package:aves/widgets/common/entry_actions.dart';
@ -185,8 +187,15 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
itemBuilder: (context) { itemBuilder: (context) {
final hasSelection = collection.selection.isNotEmpty; final hasSelection = collection.selection.isNotEmpty;
return [ return [
..._buildSortMenuItems(), PopupMenuItem(
..._buildGroupMenuItems(), value: CollectionAction.sort,
child: MenuRow(text: 'Sort...', icon: AIcons.sort),
),
if (collection.sortFactor == SortFactor.date)
PopupMenuItem(
value: CollectionAction.group,
child: MenuRow(text: 'Group...', icon: AIcons.group),
),
if (collection.isBrowsing) ...[ if (collection.isBrowsing) ...[
if (AvesApp.mode == AppMode.main) if (AvesApp.mode == AppMode.main)
if (kDebugMode) if (kDebugMode)
@ -204,6 +213,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
), ),
], ],
if (collection.isSelecting) ...[ if (collection.isSelecting) ...[
PopupMenuDivider(),
PopupMenuItem( PopupMenuItem(
value: CollectionAction.copy, value: CollectionAction.copy,
enabled: hasSelection, enabled: hasSelection,
@ -238,44 +248,6 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
]; ];
} }
List<PopupMenuEntry<CollectionAction>> _buildSortMenuItems() {
return [
PopupMenuItem(
value: CollectionAction.sortByDate,
child: MenuRow(text: 'Sort by date', checked: collection.sortFactor == SortFactor.date),
),
PopupMenuItem(
value: CollectionAction.sortBySize,
child: MenuRow(text: 'Sort by size', checked: collection.sortFactor == SortFactor.size),
),
PopupMenuItem(
value: CollectionAction.sortByName,
child: MenuRow(text: 'Sort by name', checked: collection.sortFactor == SortFactor.name),
),
PopupMenuDivider(),
];
}
List<PopupMenuEntry<CollectionAction>> _buildGroupMenuItems() {
return collection.sortFactor == SortFactor.date
? [
PopupMenuItem(
value: CollectionAction.groupByAlbum,
child: MenuRow(text: 'Group by album', checked: collection.groupFactor == GroupFactor.album),
),
PopupMenuItem(
value: CollectionAction.groupByMonth,
child: MenuRow(text: 'Group by month', checked: collection.groupFactor == GroupFactor.month),
),
PopupMenuItem(
value: CollectionAction.groupByDay,
child: MenuRow(text: 'Group by day', checked: collection.groupFactor == GroupFactor.day),
),
PopupMenuDivider(),
]
: [];
}
void _onActivityChange() { void _onActivityChange() {
if (collection.isSelecting) { if (collection.isSelecting) {
_browseToSelectAnimation.forward(); _browseToSelectAnimation.forward();
@ -317,29 +289,25 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
case CollectionAction.stats: case CollectionAction.stats:
unawaited(_goToStats()); unawaited(_goToStats());
break; break;
case CollectionAction.groupByAlbum: case CollectionAction.group:
settings.collectionGroupFactor = GroupFactor.album; final factor = await showDialog<GroupFactor>(
collection.group(GroupFactor.album); context: context,
builder: (context) => GroupCollectionDialog(),
);
if (factor != null) {
settings.collectionGroupFactor = factor;
collection.group(factor);
}
break; break;
case CollectionAction.groupByMonth: case CollectionAction.sort:
settings.collectionGroupFactor = GroupFactor.month; final factor = await showDialog<SortFactor>(
collection.group(GroupFactor.month); context: context,
break; builder: (context) => SortCollectionDialog(),
case CollectionAction.groupByDay: );
settings.collectionGroupFactor = GroupFactor.day; if (factor != null) {
collection.group(GroupFactor.day); settings.collectionSortFactor = factor;
break; collection.sort(factor);
case CollectionAction.sortByDate: }
settings.collectionSortFactor = SortFactor.date;
collection.sort(SortFactor.date);
break;
case CollectionAction.sortBySize:
settings.collectionSortFactor = SortFactor.size;
collection.sort(SortFactor.size);
break;
case CollectionAction.sortByName:
settings.collectionSortFactor = SortFactor.name;
collection.sort(SortFactor.name);
break; break;
} }
} }
@ -365,17 +333,13 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
enum CollectionAction { enum CollectionAction {
copy, copy,
group,
move, move,
refresh, refresh,
refreshMetadata, refreshMetadata,
select, select,
selectAll, selectAll,
selectNone, selectNone,
sort,
stats, stats,
groupByAlbum,
groupByMonth,
groupByDay,
sortByDate,
sortBySize,
sortByName,
} }

View file

@ -45,7 +45,9 @@ class _FilterBarState extends State<FilterBar> {
listState.removeItem( listState.removeItem(
index, index,
animate animate
? (context, animation) => FadeTransition( ? (context, animation) {
animation = animation.drive(CurveTween(curve: Curves.easeInOutBack));
return FadeTransition(
opacity: animation, opacity: animation,
child: SizeTransition( child: SizeTransition(
axis: Axis.horizontal, axis: Axis.horizontal,
@ -55,7 +57,8 @@ class _FilterBarState extends State<FilterBar> {
child: _buildChip(filter), child: _buildChip(filter),
), ),
), ),
) );
}
: (context, animation) => _buildChip(filter), : (context, animation) => _buildChip(filter),
duration: animate ? Durations.filterBarRemovalAnimation : Duration.zero, duration: animate ? Durations.filterBarRemovalAnimation : Duration.zero,
); );

View file

@ -29,18 +29,18 @@ class SectionHeader extends StatelessWidget {
Widget header; Widget header;
switch (collection.sortFactor) { switch (collection.sortFactor) {
case SortFactor.date: case SortFactor.date:
if (collection.sortFactor == SortFactor.date) { switch (collection.groupFactor) {
switch (collection.groupFactor) { case GroupFactor.album:
case GroupFactor.album: header = _buildAlbumSectionHeader();
header = _buildAlbumSectionHeader(); break;
break; case GroupFactor.month:
case GroupFactor.month: header = MonthSectionHeader(key: ValueKey(sectionKey), date: sectionKey as DateTime);
header = MonthSectionHeader(key: ValueKey(sectionKey), date: sectionKey as DateTime); break;
break; case GroupFactor.day:
case GroupFactor.day: header = DaySectionHeader(key: ValueKey(sectionKey), date: sectionKey as DateTime);
header = DaySectionHeader(key: ValueKey(sectionKey), date: sectionKey as DateTime); break;
break; case GroupFactor.none:
} break;
} }
break; break;
case SortFactor.size: case SortFactor.size:

View file

@ -9,12 +9,14 @@ class ExpandableFilterRow extends StatelessWidget {
final String title; final String title;
final Iterable<CollectionFilter> filters; final Iterable<CollectionFilter> filters;
final ValueNotifier<String> expandedNotifier; final ValueNotifier<String> expandedNotifier;
final HeroType Function(CollectionFilter filter) heroTypeBuilder;
final FilterCallback onPressed; final FilterCallback onPressed;
const ExpandableFilterRow({ const ExpandableFilterRow({
this.title, this.title,
@required this.filters, @required this.filters,
this.expandedNotifier, this.expandedNotifier,
this.heroTypeBuilder,
@required this.onPressed, @required this.onPressed,
}); });
@ -59,12 +61,7 @@ class ExpandableFilterRow extends StatelessWidget {
child: Wrap( child: Wrap(
spacing: horizontalPadding, spacing: horizontalPadding,
runSpacing: verticalPadding, runSpacing: verticalPadding,
children: filtersList children: filtersList.map(_buildFilterChip).toList(),
.map((filter) => AvesFilterChip(
filter: filter,
onPressed: onPressed,
))
.toList(),
), ),
); );
final list = Container( final list = Container(
@ -78,12 +75,7 @@ class ExpandableFilterRow extends StatelessWidget {
physics: BouncingScrollPhysics(), physics: BouncingScrollPhysics(),
padding: EdgeInsets.symmetric(horizontal: horizontalPadding), padding: EdgeInsets.symmetric(horizontal: horizontalPadding),
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (index >= filtersList.length) return null; return index < filtersList.length ? _buildFilterChip(filtersList[index]) : null;
final filter = filtersList[index];
return AvesFilterChip(
filter: filter,
onPressed: onPressed,
);
}, },
separatorBuilder: (context, index) => SizedBox(width: 8), separatorBuilder: (context, index) => SizedBox(width: 8),
itemCount: filtersList.length, itemCount: filtersList.length,
@ -109,4 +101,12 @@ class ExpandableFilterRow extends StatelessWidget {
) )
: filterChips; : filterChips;
} }
Widget _buildFilterChip(CollectionFilter filter) {
return AvesFilterChip(
filter: filter,
heroType: heroTypeBuilder?.call(filter) ?? HeroType.onTap,
onPressed: onPressed,
);
}
} }

View file

@ -62,19 +62,23 @@ class ImageSearchDelegate extends SearchDelegate<CollectionFilter> {
child: ValueListenableBuilder<String>( child: ValueListenableBuilder<String>(
valueListenable: expandedSectionNotifier, valueListenable: expandedSectionNotifier,
builder: (context, expandedSection, child) { builder: (context, expandedSection, child) {
var queryFilter = _buildQueryFilter(false);
return ListView( return ListView(
padding: EdgeInsets.only(top: 8), padding: EdgeInsets.only(top: 8),
children: [ children: [
_buildFilterRow( _buildFilterRow(
context: context, context: context,
filters: [ filters: [
_buildQueryFilter(false), queryFilter,
FavouriteFilter(), FavouriteFilter(),
MimeFilter(MimeTypes.anyImage), MimeFilter(MimeTypes.anyImage),
MimeFilter(MimeTypes.anyVideo), MimeFilter(MimeTypes.anyVideo),
MimeFilter(MimeFilter.animated), MimeFilter(MimeFilter.animated),
MimeFilter(MimeTypes.svg), MimeFilter(MimeTypes.svg),
].where((f) => f != null && containQuery(f.label)), ].where((f) => f != null && containQuery(f.label)),
// usually perform hero animation only on tapped chips,
// but we also need to animate the query chip when it is selected by submitting the search query
heroTypeBuilder: (filter) => filter == queryFilter ? HeroType.always : HeroType.onTap,
), ),
StreamBuilder( StreamBuilder(
stream: source.eventBus.on<AlbumsChangedEvent>(), stream: source.eventBus.on<AlbumsChangedEvent>(),
@ -118,11 +122,17 @@ class ImageSearchDelegate extends SearchDelegate<CollectionFilter> {
); );
} }
Widget _buildFilterRow({@required BuildContext context, String title, @required Iterable<CollectionFilter> filters}) { Widget _buildFilterRow({
@required BuildContext context,
String title,
@required Iterable<CollectionFilter> filters,
HeroType Function(CollectionFilter filter) heroTypeBuilder,
}) {
return ExpandableFilterRow( return ExpandableFilterRow(
title: title, title: title,
filters: filters, filters: filters,
expandedNotifier: expandedSectionNotifier, expandedNotifier: expandedSectionNotifier,
heroTypeBuilder: heroTypeBuilder,
onPressed: (filter) => _select(context, filter is QueryFilter ? QueryFilter(filter.query) : filter), onPressed: (filter) => _select(context, filter is QueryFilter ? QueryFilter(filter.query) : filter),
); );
} }

View file

@ -1,6 +1,7 @@
import 'dart:io'; import 'dart:io';
import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/utils/constants.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:path/path.dart'; import 'package:path/path.dart';
@ -35,56 +36,55 @@ class _CreateAlbumDialogState extends State<CreateAlbumDialog> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AlertDialog( return AlertDialog(
title: Text('New Album'), title: Text('New Album'),
content: Column( content: ListView(
mainAxisSize: MainAxisSize.min, shrinkWrap: true,
children: [ children: [
if (_allVolumes.length > 1) ...[ if (_allVolumes.length > 1) ...[
Row( Padding(
mainAxisSize: MainAxisSize.min, padding: Constants.dialogContentHorizontalPadding,
children: [ child: Text('Storage:'),
Text('Storage:'),
SizedBox(width: 8),
Expanded(
child: DropdownButton<StorageVolume>(
isExpanded: true,
items: _allVolumes
.map((volume) => DropdownMenuItem(
value: volume,
child: Text(
volume.description,
softWrap: false,
overflow: TextOverflow.fade,
maxLines: 1,
),
))
.toList(),
value: _selectedVolume,
onChanged: (volume) {
_selectedVolume = volume;
_checkAlbumExists();
setState(() {});
},
),
),
],
), ),
SizedBox(height: 16), ..._allVolumes.map((volume) => RadioListTile<StorageVolume>(
], value: volume,
ValueListenableBuilder<bool>( groupValue: _selectedVolume,
valueListenable: _existsNotifier, onChanged: (volume) {
builder: (context, exists, child) { _selectedVolume = volume;
return TextField( _checkAlbumExists();
controller: _nameController, setState(() {});
decoration: InputDecoration( },
helperText: exists ? 'Album already exists' : '', title: Text(
volume.description,
softWrap: false,
overflow: TextOverflow.fade,
maxLines: 1,
), ),
onChanged: (_) => _checkAlbumExists(), subtitle: Text(
onSubmitted: (_) => _submit(context), volume.path,
); softWrap: false,
}), overflow: TextOverflow.fade,
maxLines: 1,
),
)),
SizedBox(height: 8),
],
Padding(
padding: Constants.dialogContentHorizontalPadding,
child: ValueListenableBuilder<bool>(
valueListenable: _existsNotifier,
builder: (context, exists, child) {
return TextField(
controller: _nameController,
decoration: InputDecoration(
helperText: exists ? 'Album already exists' : '',
),
onChanged: (_) => _checkAlbumExists(),
onSubmitted: (_) => _submit(context),
);
}),
),
], ],
), ),
contentPadding: EdgeInsets.fromLTRB(24, 20, 24, 0), contentPadding: EdgeInsets.only(top: 20),
actions: [ actions: [
FlatButton( FlatButton(
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
@ -95,6 +95,8 @@ class _CreateAlbumDialogState extends State<CreateAlbumDialog> {
child: Text('Create'.toUpperCase()), child: Text('Create'.toUpperCase()),
), ),
], ],
actionsPadding: Constants.dialogActionsPadding,
shape: Constants.dialogShape,
); );
} }

View file

@ -4,6 +4,7 @@ import 'package:aves/model/image_entry.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/services/android_app_service.dart'; import 'package:aves/services/android_app_service.dart';
import 'package:aves/services/image_file_service.dart'; import 'package:aves/services/image_file_service.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/common/action_delegates/feedback.dart'; import 'package:aves/widgets/common/action_delegates/feedback.dart';
import 'package:aves/widgets/common/action_delegates/permission_aware.dart'; import 'package:aves/widgets/common/action_delegates/permission_aware.dart';
import 'package:aves/widgets/common/action_delegates/rename_entry_dialog.dart'; import 'package:aves/widgets/common/action_delegates/rename_entry_dialog.dart';
@ -131,6 +132,8 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin {
child: Text('Delete'.toUpperCase()), child: Text('Delete'.toUpperCase()),
), ),
], ],
actionsPadding: Constants.dialogActionsPadding,
shape: Constants.dialogShape,
); );
}, },
); );

View file

@ -0,0 +1,61 @@
import 'package:aves/model/settings.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/utils/constants.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class GroupCollectionDialog extends StatefulWidget {
@override
_GroupCollectionDialogState createState() => _GroupCollectionDialogState();
}
class _GroupCollectionDialogState extends State<GroupCollectionDialog> {
GroupFactor _selectedGroup;
@override
void initState() {
super.initState();
_selectedGroup = settings.collectionGroupFactor;
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Group'),
content: ListView(
shrinkWrap: true,
children: [
_buildRadioListTile(GroupFactor.album, 'By album'),
_buildRadioListTile(GroupFactor.month, 'By month'),
_buildRadioListTile(GroupFactor.day, 'By day'),
_buildRadioListTile(GroupFactor.none, 'Do not group'),
],
),
contentPadding: EdgeInsets.only(top: 20),
actions: [
FlatButton(
onPressed: () => Navigator.pop(context),
child: Text('Cancel'.toUpperCase()),
),
FlatButton(
onPressed: () => Navigator.pop(context, _selectedGroup),
child: Text('Apply'.toUpperCase()),
),
],
actionsPadding: Constants.dialogActionsPadding,
shape: Constants.dialogShape,
);
}
Widget _buildRadioListTile(GroupFactor group, String title) => RadioListTile<GroupFactor>(
value: group,
groupValue: _selectedGroup,
onChanged: (group) => setState(() => _selectedGroup = group),
title: Text(
title,
softWrap: false,
overflow: TextOverflow.fade,
maxLines: 1,
),
);
}

View file

@ -1,5 +1,6 @@
import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_entry.dart';
import 'package:aves/services/android_file_service.dart'; import 'package:aves/services/android_file_service.dart';
import 'package:aves/utils/constants.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
mixin PermissionAwareMixin { mixin PermissionAwareMixin {
@ -35,6 +36,8 @@ mixin PermissionAwareMixin {
child: Text('OK'.toUpperCase()), child: Text('OK'.toUpperCase()),
), ),
], ],
actionsPadding: Constants.dialogActionsPadding,
shape: Constants.dialogShape,
); );
}, },
); );

View file

@ -1,4 +1,5 @@
import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_entry.dart';
import 'package:aves/utils/constants.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class RenameEntryDialog extends StatefulWidget { class RenameEntryDialog extends StatefulWidget {
@ -43,6 +44,8 @@ class _RenameEntryDialogState extends State<RenameEntryDialog> {
child: Text('Apply'.toUpperCase()), child: Text('Apply'.toUpperCase()),
), ),
], ],
actionsPadding: Constants.dialogActionsPadding,
shape: Constants.dialogShape,
); );
} }
} }

View file

@ -8,6 +8,7 @@ import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/collection_source.dart';
import 'package:aves/services/android_app_service.dart'; import 'package:aves/services/android_app_service.dart';
import 'package:aves/services/image_file_service.dart'; import 'package:aves/services/image_file_service.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/utils/durations.dart'; import 'package:aves/utils/durations.dart';
import 'package:aves/widgets/album/app_bar.dart'; import 'package:aves/widgets/album/app_bar.dart';
import 'package:aves/widgets/album/empty.dart'; import 'package:aves/widgets/album/empty.dart';
@ -200,6 +201,8 @@ class SelectionActionDelegate with FeedbackMixin, PermissionAwareMixin {
child: Text('Delete'.toUpperCase()), child: Text('Delete'.toUpperCase()),
), ),
], ],
actionsPadding: Constants.dialogActionsPadding,
shape: Constants.dialogShape,
); );
}, },
); );

View file

@ -0,0 +1,60 @@
import 'package:aves/model/settings.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/utils/constants.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class SortCollectionDialog extends StatefulWidget {
@override
_SortCollectionDialogState createState() => _SortCollectionDialogState();
}
class _SortCollectionDialogState extends State<SortCollectionDialog> {
SortFactor _selectedSort;
@override
void initState() {
super.initState();
_selectedSort = settings.collectionSortFactor;
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Sort'),
content: ListView(
shrinkWrap: true,
children: [
_buildRadioListTile(SortFactor.date, 'By date'),
_buildRadioListTile(SortFactor.size, 'By size'),
_buildRadioListTile(SortFactor.name, 'By album & file name'),
],
),
contentPadding: EdgeInsets.only(top: 20),
actions: [
FlatButton(
onPressed: () => Navigator.pop(context),
child: Text('Cancel'.toUpperCase()),
),
FlatButton(
onPressed: () => Navigator.pop(context, _selectedSort),
child: Text('Apply'.toUpperCase()),
),
],
actionsPadding: Constants.dialogActionsPadding,
shape: Constants.dialogShape,
);
}
Widget _buildRadioListTile(SortFactor sort, String title) => RadioListTile<SortFactor>(
value: sort,
groupValue: _selectedSort,
onChanged: (sort) => setState(() => _selectedSort = sort),
title: Text(
title,
softWrap: false,
overflow: TextOverflow.fade,
maxLines: 1,
),
);
}

View file

@ -32,6 +32,7 @@ class AIcons {
static const IconData favourite = OMIcons.favoriteBorder; static const IconData favourite = OMIcons.favoriteBorder;
static const IconData favouriteActive = OMIcons.favorite; static const IconData favouriteActive = OMIcons.favorite;
static const IconData goUp = OMIcons.arrowUpward; static const IconData goUp = OMIcons.arrowUpward;
static const IconData group = OMIcons.groupWork;
static const IconData info = OMIcons.info; static const IconData info = OMIcons.info;
static const IconData openInNew = OMIcons.openInNew; static const IconData openInNew = OMIcons.openInNew;
static const IconData print = OMIcons.print; static const IconData print = OMIcons.print;

View file

@ -11,7 +11,7 @@ description: A new Flutter application.
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.1.1+13 version: 1.1.2+14
# video_player (as of v0.10.8+2, backed by ExoPlayer): # video_player (as of v0.10.8+2, backed by ExoPlayer):
# - does not support content URIs (by default, but trivial by fork) # - does not support content URIs (by default, but trivial by fork)