Merge branch 'develop'
This commit is contained in:
commit
041565b34a
17 changed files with 271 additions and 143 deletions
|
@ -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 }
|
||||||
|
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
);
|
);
|
||||||
|
|
|
@ -29,7 +29,6 @@ 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();
|
||||||
|
@ -40,7 +39,8 @@ class SectionHeader extends StatelessWidget {
|
||||||
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:
|
||||||
|
|
|
@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,42 +36,40 @@ 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,
|
|
||||||
),
|
),
|
||||||
))
|
..._allVolumes.map((volume) => RadioListTile<StorageVolume>(
|
||||||
.toList(),
|
value: volume,
|
||||||
value: _selectedVolume,
|
groupValue: _selectedVolume,
|
||||||
onChanged: (volume) {
|
onChanged: (volume) {
|
||||||
_selectedVolume = volume;
|
_selectedVolume = volume;
|
||||||
_checkAlbumExists();
|
_checkAlbumExists();
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
|
title: Text(
|
||||||
|
volume.description,
|
||||||
|
softWrap: false,
|
||||||
|
overflow: TextOverflow.fade,
|
||||||
|
maxLines: 1,
|
||||||
),
|
),
|
||||||
|
subtitle: Text(
|
||||||
|
volume.path,
|
||||||
|
softWrap: false,
|
||||||
|
overflow: TextOverflow.fade,
|
||||||
|
maxLines: 1,
|
||||||
),
|
),
|
||||||
|
)),
|
||||||
|
SizedBox(height: 8),
|
||||||
],
|
],
|
||||||
),
|
Padding(
|
||||||
SizedBox(height: 16),
|
padding: Constants.dialogContentHorizontalPadding,
|
||||||
],
|
child: ValueListenableBuilder<bool>(
|
||||||
ValueListenableBuilder<bool>(
|
|
||||||
valueListenable: _existsNotifier,
|
valueListenable: _existsNotifier,
|
||||||
builder: (context, exists, child) {
|
builder: (context, exists, child) {
|
||||||
return TextField(
|
return TextField(
|
||||||
|
@ -82,9 +81,10 @@ class _CreateAlbumDialogState extends State<CreateAlbumDialog> {
|
||||||
onSubmitted: (_) => _submit(context),
|
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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
|
@ -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,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue