album pick page layout fixes
This commit is contained in:
parent
6fed7b0939
commit
272916eaa6
4 changed files with 110 additions and 62 deletions
|
@ -42,5 +42,5 @@ class Durations {
|
||||||
static Duration staggeredAnimationDelay = Durations.staggeredAnimation ~/ 6 * timeDilation;
|
static Duration staggeredAnimationDelay = Durations.staggeredAnimation ~/ 6 * timeDilation;
|
||||||
static const doubleBackTimerDelay = Duration(milliseconds: 1000);
|
static const doubleBackTimerDelay = Duration(milliseconds: 1000);
|
||||||
static const softKeyboardDisplayDelay = Duration(milliseconds: 300);
|
static const softKeyboardDisplayDelay = Duration(milliseconds: 300);
|
||||||
static const searchDebounceDelay = Duration(milliseconds: 200);
|
static const searchDebounceDelay = Duration(milliseconds: 250);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,21 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:aves/model/filters/album.dart';
|
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
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/model/source/enums.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/widgets/collection/collection_actions.dart';
|
import 'package:aves/widgets/collection/collection_actions.dart';
|
||||||
import 'package:aves/widgets/collection/empty.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/size_aware.dart';
|
import 'package:aves/widgets/common/action_delegates/size_aware.dart';
|
||||||
import 'package:aves/widgets/common/aves_dialog.dart';
|
import 'package:aves/widgets/common/aves_dialog.dart';
|
||||||
import 'package:aves/widgets/common/entry_actions.dart';
|
import 'package:aves/widgets/common/entry_actions.dart';
|
||||||
import 'package:aves/widgets/common/icons.dart';
|
|
||||||
import 'package:aves/widgets/filter_grids/album_pick.dart';
|
import 'package:aves/widgets/filter_grids/album_pick.dart';
|
||||||
import 'package:aves/widgets/filter_grids/albums_page.dart';
|
|
||||||
import 'package:aves/widgets/filter_grids/common/chip_set_action_delegate.dart';
|
|
||||||
import 'package:aves/widgets/filter_grids/common/filter_grid_page.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
class SelectionActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
class SelectionActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
||||||
final CollectionLens collection;
|
final CollectionLens collection;
|
||||||
|
@ -71,38 +62,11 @@ class SelectionActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwar
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _moveSelection(BuildContext context, {@required bool copy}) async {
|
Future<void> _moveSelection(BuildContext context, {@required bool copy}) async {
|
||||||
final filterNotifier = ValueNotifier('');
|
|
||||||
final chipSetActionDelegate = AlbumChipSetActionDelegate(source: source);
|
|
||||||
final destinationAlbum = await Navigator.push(
|
final destinationAlbum = await Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute<String>(
|
MaterialPageRoute<String>(
|
||||||
builder: (context) {
|
settings: RouteSettings(name: AlbumPickPage.routeName),
|
||||||
Widget appBar = AlbumPickAppBar(
|
builder: (context) => AlbumPickPage(source: source, copy: copy),
|
||||||
copy: copy,
|
|
||||||
actionDelegate: chipSetActionDelegate,
|
|
||||||
onFilterChanged: (filter) => filterNotifier.value = filter,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Selector<Settings, ChipSortFactor>(
|
|
||||||
selector: (context, s) => s.albumSortFactor,
|
|
||||||
builder: (context, sortFactor, child) {
|
|
||||||
return ValueListenableBuilder<String>(
|
|
||||||
valueListenable: filterNotifier,
|
|
||||||
builder: (context, filter, child) => FilterGridPage(
|
|
||||||
source: source,
|
|
||||||
appBar: appBar,
|
|
||||||
filterEntries: AlbumListPage.getAlbumEntries(source, filter: filter),
|
|
||||||
filterBuilder: (s) => AlbumFilter(s, source.getUniqueAlbumName(s)),
|
|
||||||
emptyBuilder: () => EmptyContent(
|
|
||||||
icon: AIcons.album,
|
|
||||||
text: 'No albums',
|
|
||||||
),
|
|
||||||
onTap: (filter) => Navigator.pop<String>(context, (filter as AlbumFilter)?.album),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (destinationAlbum == null || destinationAlbum.isEmpty) return;
|
if (destinationAlbum == null || destinationAlbum.isEmpty) return;
|
||||||
|
|
|
@ -1,22 +1,83 @@
|
||||||
|
import 'package:aves/model/filters/album.dart';
|
||||||
|
import 'package:aves/model/settings/settings.dart';
|
||||||
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
|
import 'package:aves/model/source/enums.dart';
|
||||||
import 'package:aves/utils/debouncer.dart';
|
import 'package:aves/utils/debouncer.dart';
|
||||||
import 'package:aves/utils/durations.dart';
|
import 'package:aves/utils/durations.dart';
|
||||||
|
import 'package:aves/widgets/collection/empty.dart';
|
||||||
import 'package:aves/widgets/common/action_delegates/create_album_dialog.dart';
|
import 'package:aves/widgets/common/action_delegates/create_album_dialog.dart';
|
||||||
import 'package:aves/widgets/common/icons.dart';
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
|
import 'package:aves/widgets/filter_grids/albums_page.dart';
|
||||||
import 'package:aves/widgets/filter_grids/common/chip_actions.dart';
|
import 'package:aves/widgets/filter_grids/common/chip_actions.dart';
|
||||||
import 'package:aves/widgets/filter_grids/common/chip_set_action_delegate.dart';
|
import 'package:aves/widgets/filter_grids/common/chip_set_action_delegate.dart';
|
||||||
|
import 'package:aves/widgets/filter_grids/common/filter_grid_page.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class AlbumPickPage extends StatefulWidget {
|
||||||
|
static const routeName = '/album_pick';
|
||||||
|
|
||||||
|
final CollectionSource source;
|
||||||
|
final bool copy;
|
||||||
|
|
||||||
|
const AlbumPickPage({
|
||||||
|
@required this.source,
|
||||||
|
@required this.copy,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_AlbumPickPageState createState() => _AlbumPickPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AlbumPickPageState extends State<AlbumPickPage> {
|
||||||
|
final _filterNotifier = ValueNotifier('');
|
||||||
|
|
||||||
|
CollectionSource get source => widget.source;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Widget appBar = AlbumPickAppBar(
|
||||||
|
copy: widget.copy,
|
||||||
|
actionDelegate: AlbumChipSetActionDelegate(source: source),
|
||||||
|
filterNotifier: _filterNotifier,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Selector<Settings, ChipSortFactor>(
|
||||||
|
selector: (context, s) => s.albumSortFactor,
|
||||||
|
builder: (context, sortFactor, child) {
|
||||||
|
return ValueListenableBuilder<String>(
|
||||||
|
valueListenable: _filterNotifier,
|
||||||
|
builder: (context, filter, child) => FilterGridPage(
|
||||||
|
source: source,
|
||||||
|
appBar: appBar,
|
||||||
|
filterEntries: AlbumListPage.getAlbumEntries(source, filter: filter),
|
||||||
|
filterBuilder: (s) => AlbumFilter(s, source.getUniqueAlbumName(s)),
|
||||||
|
emptyBuilder: () => EmptyContent(
|
||||||
|
icon: AIcons.album,
|
||||||
|
text: 'No albums',
|
||||||
|
),
|
||||||
|
appBarHeight: AlbumPickAppBar.preferredHeight,
|
||||||
|
onTap: (filter) => Navigator.pop<String>(context, (filter as AlbumFilter)?.album),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class AlbumPickAppBar extends StatelessWidget {
|
class AlbumPickAppBar extends StatelessWidget {
|
||||||
final bool copy;
|
final bool copy;
|
||||||
final AlbumChipSetActionDelegate actionDelegate;
|
final AlbumChipSetActionDelegate actionDelegate;
|
||||||
final ValueChanged<String> onFilterChanged;
|
final ValueNotifier<String> filterNotifier;
|
||||||
|
|
||||||
|
static const preferredHeight = kToolbarHeight + AlbumFilterBar.preferredHeight;
|
||||||
|
|
||||||
const AlbumPickAppBar({
|
const AlbumPickAppBar({
|
||||||
@required this.copy,
|
@required this.copy,
|
||||||
@required this.actionDelegate,
|
@required this.actionDelegate,
|
||||||
@required this.onFilterChanged,
|
@required this.filterNotifier,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -25,7 +86,7 @@ class AlbumPickAppBar extends StatelessWidget {
|
||||||
leading: BackButton(),
|
leading: BackButton(),
|
||||||
title: Text(copy ? 'Copy to Album' : 'Move to Album'),
|
title: Text(copy ? 'Copy to Album' : 'Move to Album'),
|
||||||
bottom: AlbumFilterBar(
|
bottom: AlbumFilterBar(
|
||||||
onChanged: onFilterChanged,
|
filterNotifier: filterNotifier,
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
|
@ -53,20 +114,30 @@ class AlbumPickAppBar extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class AlbumFilterBar extends StatefulWidget implements PreferredSizeWidget {
|
class AlbumFilterBar extends StatefulWidget implements PreferredSizeWidget {
|
||||||
final ValueChanged<String> onChanged;
|
final ValueNotifier<String> filterNotifier;
|
||||||
|
|
||||||
const AlbumFilterBar({@required this.onChanged});
|
static const preferredHeight = kToolbarHeight;
|
||||||
|
|
||||||
|
const AlbumFilterBar({@required this.filterNotifier});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Size get preferredSize => Size.fromHeight(kToolbarHeight);
|
Size get preferredSize => Size.fromHeight(preferredHeight);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_AlbumFilterBarState createState() => _AlbumFilterBarState();
|
_AlbumFilterBarState createState() => _AlbumFilterBarState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AlbumFilterBarState extends State<AlbumFilterBar> {
|
class _AlbumFilterBarState extends State<AlbumFilterBar> {
|
||||||
final TextEditingController _controller = TextEditingController(text: '');
|
|
||||||
final Debouncer _debouncer = Debouncer(delay: Durations.searchDebounceDelay);
|
final Debouncer _debouncer = Debouncer(delay: Durations.searchDebounceDelay);
|
||||||
|
TextEditingController _controller;
|
||||||
|
|
||||||
|
ValueNotifier<String> get filterNotifier => widget.filterNotifier;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_controller = TextEditingController(text: filterNotifier.value);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -74,12 +145,12 @@ class _AlbumFilterBarState extends State<AlbumFilterBar> {
|
||||||
icon: Icon(AIcons.clear),
|
icon: Icon(AIcons.clear),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_controller.clear();
|
_controller.clear();
|
||||||
widget.onChanged('');
|
filterNotifier.value = '';
|
||||||
},
|
},
|
||||||
tooltip: 'Clear',
|
tooltip: 'Clear',
|
||||||
);
|
);
|
||||||
return Container(
|
return Container(
|
||||||
height: kToolbarHeight,
|
height: AlbumFilterBar.preferredHeight,
|
||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
@ -98,22 +169,25 @@ class _AlbumFilterBarState extends State<AlbumFilterBar> {
|
||||||
hintStyle: Theme.of(context).inputDecorationTheme.hintStyle,
|
hintStyle: Theme.of(context).inputDecorationTheme.hintStyle,
|
||||||
),
|
),
|
||||||
textInputAction: TextInputAction.search,
|
textInputAction: TextInputAction.search,
|
||||||
onChanged: (s) => _debouncer(() => widget.onChanged(s)),
|
onChanged: (s) => _debouncer(() => filterNotifier.value = s),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
AnimatedBuilder(
|
ConstrainedBox(
|
||||||
animation: _controller,
|
constraints: BoxConstraints(minWidth: 16),
|
||||||
builder: (context, child) => AnimatedSwitcher(
|
child: AnimatedBuilder(
|
||||||
duration: Durations.appBarActionChangeAnimation,
|
animation: _controller,
|
||||||
transitionBuilder: (child, animation) => FadeTransition(
|
builder: (context, child) => AnimatedSwitcher(
|
||||||
opacity: animation,
|
duration: Durations.appBarActionChangeAnimation,
|
||||||
child: SizeTransition(
|
transitionBuilder: (child, animation) => FadeTransition(
|
||||||
axis: Axis.horizontal,
|
opacity: animation,
|
||||||
sizeFactor: animation,
|
child: SizeTransition(
|
||||||
child: child,
|
axis: Axis.horizontal,
|
||||||
|
sizeFactor: animation,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
child: _controller.text.isNotEmpty ? clearButton : SizedBox.shrink(),
|
||||||
),
|
),
|
||||||
child: _controller.text.isNotEmpty ? clearButton : SizedBox(width: 16),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|
|
@ -170,6 +170,7 @@ class FilterGridPage extends StatelessWidget {
|
||||||
final Map<String, ImageEntry> filterEntries;
|
final Map<String, ImageEntry> filterEntries;
|
||||||
final CollectionFilter Function(String key) filterBuilder;
|
final CollectionFilter Function(String key) filterBuilder;
|
||||||
final Widget Function() emptyBuilder;
|
final Widget Function() emptyBuilder;
|
||||||
|
final double appBarHeight;
|
||||||
final FilterCallback onTap;
|
final FilterCallback onTap;
|
||||||
final OffsetFilterCallback onLongPress;
|
final OffsetFilterCallback onLongPress;
|
||||||
|
|
||||||
|
@ -179,6 +180,7 @@ class FilterGridPage extends StatelessWidget {
|
||||||
@required this.filterEntries,
|
@required this.filterEntries,
|
||||||
@required this.filterBuilder,
|
@required this.filterBuilder,
|
||||||
@required this.emptyBuilder,
|
@required this.emptyBuilder,
|
||||||
|
this.appBarHeight = kToolbarHeight,
|
||||||
@required this.onTap,
|
@required this.onTap,
|
||||||
this.onLongPress,
|
this.onLongPress,
|
||||||
});
|
});
|
||||||
|
@ -227,7 +229,7 @@ class FilterGridPage extends StatelessWidget {
|
||||||
controller: PrimaryScrollController.of(context),
|
controller: PrimaryScrollController.of(context),
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
// padding to keep scroll thumb between app bar above and nav bar below
|
// padding to keep scroll thumb between app bar above and nav bar below
|
||||||
top: kToolbarHeight,
|
top: appBarHeight,
|
||||||
bottom: mqViewInsetsBottom,
|
bottom: mqViewInsetsBottom,
|
||||||
),
|
),
|
||||||
child: scrollView,
|
child: scrollView,
|
||||||
|
@ -243,7 +245,15 @@ class FilterGridPage extends StatelessWidget {
|
||||||
appBar,
|
appBar,
|
||||||
filterKeys.isEmpty
|
filterKeys.isEmpty
|
||||||
? SliverFillRemaining(
|
? SliverFillRemaining(
|
||||||
child: emptyBuilder(),
|
child: Selector<MediaQueryData, double>(
|
||||||
|
selector: (context, mq) => mq.viewInsets.bottom,
|
||||||
|
builder: (context, mqViewInsetsBottom, child) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: mqViewInsetsBottom),
|
||||||
|
child: emptyBuilder(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
hasScrollBody: false,
|
hasScrollBody: false,
|
||||||
)
|
)
|
||||||
: SliverPadding(
|
: SliverPadding(
|
||||||
|
|
Loading…
Reference in a new issue