shortcut: select icon image
This commit is contained in:
parent
562f3057ed
commit
bbe1f496d2
3 changed files with 125 additions and 36 deletions
|
@ -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
|
||||
|
|
|
@ -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<double> appBarHeightNotifier;
|
||||
|
@ -347,22 +349,25 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
Future<void> _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<CollectionFilter>.from(filters)..sort();
|
||||
defaultName = sortedFilters.first.getLabel(context);
|
||||
}
|
||||
final name = await showDialog<String>(
|
||||
final result = await showDialog<Tuple2<AvesEntry, String>>(
|
||||
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() {
|
||||
|
|
|
@ -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<AddShortcutDialog> {
|
||||
final TextEditingController _nameController = TextEditingController();
|
||||
final ValueNotifier<bool> _isValidNotifier = ValueNotifier(false);
|
||||
AvesEntry _coverEntry;
|
||||
|
||||
CollectionLens get collection => widget.collection;
|
||||
|
||||
Set<CollectionFilter> 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<AddShortcutDialog> {
|
|||
|
||||
@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<MediaQueryData, double>((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<bool>(
|
||||
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<bool>(
|
||||
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<void> _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<void> _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<AvesEntry, String>(_coverEntry, _nameController.text));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue