From bbe1f496d25db0dad772712b94eb8cf2bf243042 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Wed, 24 Mar 2021 16:23:50 +0900 Subject: [PATCH] shortcut: select icon image --- CHANGELOG.md | 1 + lib/widgets/collection/app_bar.dart | 23 ++-- lib/widgets/dialogs/add_shortcut_dialog.dart | 137 +++++++++++++++---- 3 files changed, 125 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10c443505..a1e477e93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. ### Added - Collection / Albums / Countries / Tags: added label when dragging scrollbar thumb - Albums: localized common album names +- Collection: select shortcut icon image ### Changed - Upgraded Flutter to beta v2.1.0-12.2.pre diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart index 860b4b42a..7c18ac952 100644 --- a/lib/widgets/collection/app_bar.dart +++ b/lib/widgets/collection/app_bar.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:aves/app_mode.dart'; import 'package:aves/model/actions/collection_actions.dart'; import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/entry.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; @@ -27,6 +28,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:pedantic/pedantic.dart'; import 'package:provider/provider.dart'; +import 'package:tuple/tuple.dart'; class CollectionAppBar extends StatefulWidget { final ValueNotifier appBarHeightNotifier; @@ -347,22 +349,25 @@ class _CollectionAppBarState extends State with SingleTickerPr Future _showShortcutDialog(BuildContext context) async { final filters = collection.filters; var defaultName; - if (filters.isEmpty) { - defaultName = context.l10n.collectionPageTitle; - } else { + if (filters.isNotEmpty) { + // we compute the default name beforehand + // because some filter labels need localization final sortedFilters = List.from(filters)..sort(); defaultName = sortedFilters.first.getLabel(context); } - final name = await showDialog( + final result = await showDialog>( context: context, - builder: (context) { - return AddShortcutDialog(defaultName: defaultName); - }, + builder: (context) => AddShortcutDialog( + collection: collection, + defaultName: defaultName, + ), ); + final coverEntry = result.item1; + final name = result.item2; + if (name == null || name.isEmpty) return; - final iconEntry = collection.sortedEntries.isNotEmpty ? collection.sortedEntries.first : null; - unawaited(AppShortcutService.pin(name, iconEntry, filters)); + unawaited(AppShortcutService.pin(name, coverEntry, collection.filters)); } void _goToSearch() { diff --git a/lib/widgets/dialogs/add_shortcut_dialog.dart b/lib/widgets/dialogs/add_shortcut_dialog.dart index a12edb60c..8dd050a0b 100644 --- a/lib/widgets/dialogs/add_shortcut_dialog.dart +++ b/lib/widgets/dialogs/add_shortcut_dialog.dart @@ -1,12 +1,24 @@ +import 'package:aves/model/covers.dart'; +import 'package:aves/model/entry.dart'; +import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/source/collection_lens.dart'; +import 'package:aves/widgets/collection/thumbnail/raster.dart'; +import 'package:aves/widgets/collection/thumbnail/vector.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; +import 'package:aves/widgets/dialogs/item_pick_dialog.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:tuple/tuple.dart'; import 'aves_dialog.dart'; class AddShortcutDialog extends StatefulWidget { + final CollectionLens collection; final String defaultName; const AddShortcutDialog({ + @required this.collection, @required this.defaultName, }); @@ -17,10 +29,20 @@ class AddShortcutDialog extends StatefulWidget { class _AddShortcutDialogState extends State { final TextEditingController _nameController = TextEditingController(); final ValueNotifier _isValidNotifier = ValueNotifier(false); + AvesEntry _coverEntry; + + CollectionLens get collection => widget.collection; + + Set get filters => collection.filters; @override void initState() { super.initState(); + final entries = collection.sortedEntries; + if (entries.isNotEmpty) { + final coverEntries = filters.map(covers.coverContentId).where((id) => id != null).map((id) => entries.firstWhere((entry) => entry.contentId == id, orElse: () => null)).where((entry) => entry != null); + _coverEntry = coverEntries.isNotEmpty ? coverEntries.first : entries.first; + } _nameController.text = widget.defaultName; _validate(); } @@ -33,40 +55,101 @@ class _AddShortcutDialogState extends State { @override Widget build(BuildContext context) { - return AvesDialog( - context: context, - content: TextField( - controller: _nameController, - decoration: InputDecoration( - labelText: context.l10n.addShortcutDialogLabel, - ), - autofocus: true, - maxLength: 25, - onChanged: (_) => _validate(), - onSubmitted: (_) => _submit(context), + return MediaQueryDataProvider( + child: Builder( + builder: (context) { + final shortestSide = context.select((mq) => mq.size.shortestSide); + final extent = (shortestSide / 3.0).clamp(60.0, 160.0); + return AvesDialog( + context: context, + scrollableContent: [ + if (_coverEntry != null) + Container( + alignment: Alignment.center, + padding: EdgeInsets.only(top: 16), + child: _buildCover(_coverEntry, extent), + ), + Padding( + padding: EdgeInsets.symmetric(vertical: 8, horizontal: 24), + child: TextField( + controller: _nameController, + decoration: InputDecoration( + labelText: context.l10n.addShortcutDialogLabel, + ), + autofocus: true, + maxLength: 25, + onChanged: (_) => _validate(), + onSubmitted: (_) => _submit(context), + ), + ), + ], + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(MaterialLocalizations.of(context).cancelButtonLabel), + ), + ValueListenableBuilder( + valueListenable: _isValidNotifier, + builder: (context, isValid, child) { + return TextButton( + onPressed: isValid ? () => _submit(context) : null, + child: Text(context.l10n.addShortcutButtonLabel), + ); + }, + ) + ], + ); + }, ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), - ValueListenableBuilder( - valueListenable: _isValidNotifier, - builder: (context, isValid, child) { - return TextButton( - onPressed: isValid ? () => _submit(context) : null, - child: Text(context.l10n.addShortcutButtonLabel), - ); - }, - ) - ], ); } + Widget _buildCover(AvesEntry entry, double extent) { + return GestureDetector( + onTap: _pickEntry, + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(32)), + child: SizedBox( + width: extent, + height: extent, + child: entry.isSvg + ? VectorImageThumbnail( + entry: entry, + extent: extent, + ) + : RasterImageThumbnail( + entry: entry, + extent: extent, + ), + ), + ), + ); + } + + Future _pickEntry() async { + final entry = await Navigator.push( + context, + MaterialPageRoute( + settings: RouteSettings(name: ItemPickDialog.routeName), + builder: (context) => ItemPickDialog( + CollectionLens( + source: collection.source, + filters: filters, + ), + ), + fullscreenDialog: true, + ), + ); + if (entry != null) { + _coverEntry = entry; + setState(() {}); + } + } + Future _validate() async { final name = _nameController.text ?? ''; _isValidNotifier.value = name.isNotEmpty; } - void _submit(BuildContext context) => Navigator.pop(context, _nameController.text); + void _submit(BuildContext context) => Navigator.pop(context, Tuple2(_coverEntry, _nameController.text)); }