info: easier access to rating/tag edition

This commit is contained in:
Thibault Deckers 2022-01-06 12:54:38 +09:00
parent 05dc8beec0
commit 1fc9fb040e
10 changed files with 81 additions and 58 deletions

View file

@ -50,9 +50,9 @@ extension ExtraEntryInfoAction on EntryInfoAction {
case EntryInfoAction.editDate: case EntryInfoAction.editDate:
return AIcons.date; return AIcons.date;
case EntryInfoAction.editRating: case EntryInfoAction.editRating:
return AIcons.rating; return AIcons.editRating;
case EntryInfoAction.editTags: case EntryInfoAction.editTags:
return AIcons.addTag; return AIcons.editTags;
case EntryInfoAction.removeMetadata: case EntryInfoAction.removeMetadata:
return AIcons.clear; return AIcons.clear;
// motion photo // motion photo

View file

@ -167,9 +167,9 @@ extension ExtraEntrySetAction on EntrySetAction {
case EntrySetAction.editDate: case EntrySetAction.editDate:
return AIcons.date; return AIcons.date;
case EntrySetAction.editRating: case EntrySetAction.editRating:
return AIcons.rating; return AIcons.editRating;
case EntrySetAction.editTags: case EntrySetAction.editTags:
return AIcons.addTag; return AIcons.editTags;
case EntrySetAction.removeMetadata: case EntrySetAction.removeMetadata:
return AIcons.clear; return AIcons.clear;
} }

View file

@ -1,9 +1,13 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@immutable @immutable
class ActionEvent<T> { class ActionEvent<T> extends Equatable {
final T action; final T action;
@override
List<Object?> get props => [action];
const ActionEvent(this.action); const ActionEvent(this.action);
} }

View file

@ -69,7 +69,7 @@ class LocationFilter extends CollectionFilter {
); );
} }
} }
return Icon(_location.isEmpty ? AIcons.locationOff : AIcons.location, size: size); return Icon(_location.isEmpty ? AIcons.locationUnlocated : AIcons.location, size: size);
} }
@override @override

View file

@ -44,7 +44,7 @@ class TagFilter extends CollectionFilter {
String getLabel(BuildContext context) => tag.isEmpty ? context.l10n.filterTagEmptyLabel : tag; String getLabel(BuildContext context) => tag.isEmpty ? context.l10n.filterTagEmptyLabel : tag;
@override @override
Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => showGenericIcon ? Icon(tag.isEmpty ? AIcons.tagOff : AIcons.tag, size: size) : null; Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => showGenericIcon ? Icon(tag.isEmpty ? AIcons.tagUntagged : AIcons.tag, size: size) : null;
@override @override
String get category => type; String get category => type;

View file

@ -19,7 +19,7 @@ class AIcons {
static const IconData home = Icons.home_outlined; static const IconData home = Icons.home_outlined;
static const IconData language = Icons.translate_outlined; static const IconData language = Icons.translate_outlined;
static const IconData location = Icons.place_outlined; static const IconData location = Icons.place_outlined;
static const IconData locationOff = Icons.location_off_outlined; static const IconData locationUnlocated = Icons.location_off_outlined;
static const IconData mainStorage = Icons.smartphone_outlined; static const IconData mainStorage = Icons.smartphone_outlined;
static const IconData privacy = MdiIcons.shieldAccountOutline; static const IconData privacy = MdiIcons.shieldAccountOutline;
static const IconData rating = Icons.star_border_outlined; static const IconData rating = Icons.star_border_outlined;
@ -29,12 +29,12 @@ class AIcons {
static const IconData raw = Icons.raw_on_outlined; static const IconData raw = Icons.raw_on_outlined;
static const IconData shooting = Icons.camera_outlined; static const IconData shooting = Icons.camera_outlined;
static const IconData removableStorage = Icons.sd_storage_outlined; static const IconData removableStorage = Icons.sd_storage_outlined;
static const IconData sensorControl = Icons.explore_outlined; static const IconData sensorControlEnabled = Icons.explore_outlined;
static const IconData sensorControlOff = Icons.explore_off_outlined; static const IconData sensorControlDisabled = Icons.explore_off_outlined;
static const IconData settings = Icons.settings_outlined; static const IconData settings = Icons.settings_outlined;
static const IconData text = Icons.format_quote_outlined; static const IconData text = Icons.format_quote_outlined;
static const IconData tag = Icons.local_offer_outlined; static const IconData tag = Icons.local_offer_outlined;
static const IconData tagOff = MdiIcons.tagOffOutline; static const IconData tagUntagged = MdiIcons.tagOffOutline;
// view // view
static const IconData group = Icons.group_work_outlined; static const IconData group = Icons.group_work_outlined;
@ -44,7 +44,6 @@ class AIcons {
// actions // actions
static const IconData add = Icons.add_circle_outline; static const IconData add = Icons.add_circle_outline;
static const IconData addShortcut = Icons.add_to_home_screen_outlined; static const IconData addShortcut = Icons.add_to_home_screen_outlined;
static const IconData addTag = MdiIcons.tagPlusOutline;
static const IconData cancel = Icons.cancel_outlined; static const IconData cancel = Icons.cancel_outlined;
static const IconData replay10 = Icons.replay_10_outlined; static const IconData replay10 = Icons.replay_10_outlined;
static const IconData skip10 = Icons.forward_10_outlined; static const IconData skip10 = Icons.forward_10_outlined;
@ -55,6 +54,8 @@ class AIcons {
static const IconData debug = Icons.whatshot_outlined; static const IconData debug = Icons.whatshot_outlined;
static const IconData delete = Icons.delete_outlined; static const IconData delete = Icons.delete_outlined;
static const IconData edit = Icons.edit_outlined; static const IconData edit = Icons.edit_outlined;
static const IconData editRating = MdiIcons.starPlusOutline;
static const IconData editTags = MdiIcons.tagPlusOutline;
static const IconData export = MdiIcons.fileExportOutline; static const IconData export = MdiIcons.fileExportOutline;
static const IconData flip = Icons.flip_outlined; static const IconData flip = Icons.flip_outlined;
static const IconData favourite = Icons.favorite_border; static const IconData favourite = Icons.favorite_border;

View file

@ -122,7 +122,7 @@ class _TagEditorPageState extends State<TagEditorPage> {
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Icon(AIcons.tagOff, color: untaggedColor), const Icon(AIcons.tagUntagged, color: untaggedColor),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
l10n.filterTagEmptyLabel, l10n.filterTagEmptyLabel,

View file

@ -9,7 +9,6 @@ import 'package:aves/model/filters/tag.dart';
import 'package:aves/model/filters/type.dart'; import 'package:aves/model/filters/type.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/theme/format.dart'; import 'package:aves/theme/format.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/utils/file_utils.dart'; import 'package:aves/utils/file_utils.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
@ -24,7 +23,7 @@ class BasicSection extends StatelessWidget {
final AvesEntry entry; final AvesEntry entry;
final CollectionLens? collection; final CollectionLens? collection;
final EntryInfoActionDelegate actionDelegate; final EntryInfoActionDelegate actionDelegate;
final ValueNotifier<bool> isEditingTagNotifier; final ValueNotifier<EntryInfoAction?> isEditingMetadataNotifier;
final FilterCallback onFilter; final FilterCallback onFilter;
const BasicSection({ const BasicSection({
@ -32,7 +31,7 @@ class BasicSection extends StatelessWidget {
required this.entry, required this.entry,
this.collection, this.collection,
required this.actionDelegate, required this.actionDelegate,
required this.isEditingTagNotifier, required this.isEditingMetadataNotifier,
required this.onFilter, required this.onFilter,
}) : super(key: key); }) : super(key: key);
@ -77,6 +76,7 @@ class BasicSection extends StatelessWidget {
), ),
OwnerProp(entry: entry), OwnerProp(entry: entry),
_buildChips(context), _buildChips(context),
_buildEditButtons(context),
], ],
); );
}); });
@ -106,58 +106,78 @@ class BasicSection extends StatelessWidget {
if (entry.isFavourite) FavouriteFilter.instance, if (entry.isFavourite) FavouriteFilter.instance,
]..sort(); ]..sort();
final children = <Widget>[ return Padding(
...effectiveFilters.map((filter) => AvesFilterChip( padding: const EdgeInsets.symmetric(horizontal: AvesFilterChip.outlineWidth / 2) + const EdgeInsets.only(top: 8),
filter: filter, child: Wrap(
onTap: onFilter, spacing: 8,
)), runSpacing: 8,
if (actionDelegate.canApply(EntryInfoAction.editTags)) _buildEditTagButton(context), children: effectiveFilters
]; .map((filter) => AvesFilterChip(
filter: filter,
return children.isEmpty onTap: onFilter,
? const SizedBox() ))
: Padding( .toList(),
padding: const EdgeInsets.symmetric(horizontal: AvesFilterChip.outlineWidth / 2) + const EdgeInsets.only(top: 8), ),
child: Wrap( );
spacing: 8,
runSpacing: 8,
children: children,
),
);
}, },
); );
} }
Widget _buildEditTagButton(BuildContext context) { Widget _buildEditButtons(BuildContext context) {
const action = EntryInfoAction.editTags; final children = [
return ValueListenableBuilder<bool>( EntryInfoAction.editRating,
valueListenable: isEditingTagNotifier, EntryInfoAction.editTags,
builder: (context, isEditing, child) { ].where(actionDelegate.canApply).map((v) => _buildEditMetadataButton(context, v)).toList();
return children.isEmpty
? const SizedBox()
: TooltipTheme(
data: TooltipTheme.of(context).copyWith(
preferBelow: false,
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: AvesFilterChip.outlineWidth / 2) + const EdgeInsets.only(top: 8),
child: Wrap(
spacing: 8,
runSpacing: 8,
children: children,
),
),
);
}
Widget _buildEditMetadataButton(BuildContext context, EntryInfoAction action) {
return ValueListenableBuilder<EntryInfoAction?>(
valueListenable: isEditingMetadataNotifier,
builder: (context, editingAction, child) {
final isEditing = editingAction != null;
return Stack( return Stack(
children: [ children: [
DecoratedBox( DecoratedBox(
decoration: const BoxDecoration( decoration: BoxDecoration(
border: Border.fromBorderSide(BorderSide( border: Border.fromBorderSide(BorderSide(
color: AvesFilterChip.defaultOutlineColor, color: isEditing ? Theme.of(context).disabledColor : AvesFilterChip.defaultOutlineColor,
width: AvesFilterChip.outlineWidth, width: AvesFilterChip.outlineWidth,
)), )),
borderRadius: BorderRadius.all(Radius.circular(AvesFilterChip.defaultRadius)), borderRadius: const BorderRadius.all(Radius.circular(AvesFilterChip.defaultRadius)),
), ),
child: IconButton( child: IconButton(
icon: const Icon(AIcons.addTag), icon: action.getIcon(),
onPressed: isEditing ? null : () => actionDelegate.onActionSelected(context, action), onPressed: isEditing ? null : () => actionDelegate.onActionSelected(context, action),
tooltip: action.getText(context), tooltip: action.getText(context),
), ),
), ),
if (isEditing) Positioned.fill(
const Positioned.fill( child: Visibility(
child: Padding( visible: editingAction == action,
child: const Padding(
padding: EdgeInsets.all(1.0), padding: EdgeInsets.all(1.0),
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: AvesFilterChip.outlineWidth, strokeWidth: AvesFilterChip.outlineWidth,
), ),
), ),
), ),
),
], ],
); );
}, },

View file

@ -150,7 +150,7 @@ class _InfoPageContentState extends State<_InfoPageContent> {
final List<StreamSubscription> _subscriptions = []; final List<StreamSubscription> _subscriptions = [];
late EntryInfoActionDelegate _actionDelegate; late EntryInfoActionDelegate _actionDelegate;
final ValueNotifier<Map<String, MetadataDirectory>> _metadataNotifier = ValueNotifier({}); final ValueNotifier<Map<String, MetadataDirectory>> _metadataNotifier = ValueNotifier({});
final ValueNotifier<bool> _isEditingTagNotifier = ValueNotifier(false); final ValueNotifier<EntryInfoAction?> _isEditingMetadataNotifier = ValueNotifier(null);
static const horizontalPadding = EdgeInsets.symmetric(horizontal: 8); static const horizontalPadding = EdgeInsets.symmetric(horizontal: 8);
@ -197,7 +197,7 @@ class _InfoPageContentState extends State<_InfoPageContent> {
entry: entry, entry: entry,
collection: collection, collection: collection,
actionDelegate: _actionDelegate, actionDelegate: _actionDelegate,
isEditingTagNotifier: _isEditingTagNotifier, isEditingMetadataNotifier: _isEditingMetadataNotifier,
onFilter: _goToCollection, onFilter: _goToCollection,
); );
final locationAtTop = widget.split && entry.hasGps; final locationAtTop = widget.split && entry.hasGps;
@ -255,15 +255,13 @@ class _InfoPageContentState extends State<_InfoPageContent> {
} }
void _onActionDelegateEvent(ActionEvent<EntryInfoAction> event) { void _onActionDelegateEvent(ActionEvent<EntryInfoAction> event) {
if (event.action == EntryInfoAction.editTags) { Future.delayed(Durations.dialogTransitionAnimation).then((_) {
Future.delayed(Durations.dialogTransitionAnimation).then((_) { if (event is ActionStartedEvent) {
if (event is ActionStartedEvent) { _isEditingMetadataNotifier.value = event.action;
_isEditingTagNotifier.value = true; } else if (event is ActionEndedEvent) {
} else if (event is ActionEndedEvent) { _isEditingMetadataNotifier.value = null;
_isEditingTagNotifier.value = false; }
} });
});
}
} }
void _goToCollection(CollectionFilter filter) { void _goToCollection(CollectionFilter filter) {

View file

@ -109,7 +109,7 @@ class _PanoramaPageState extends State<PanoramaPage> {
valueListenable: _sensorControl, valueListenable: _sensorControl,
builder: (context, sensorControl, child) { builder: (context, sensorControl, child) {
return IconButton( return IconButton(
icon: Icon(sensorControl == SensorControl.None ? AIcons.sensorControl : AIcons.sensorControlOff), icon: Icon(sensorControl == SensorControl.None ? AIcons.sensorControlEnabled : AIcons.sensorControlDisabled),
onPressed: _toggleSensor, onPressed: _toggleSensor,
tooltip: sensorControl == SensorControl.None ? context.l10n.panoramaEnableSensorControl : context.l10n.panoramaDisableSensorControl, tooltip: sensorControl == SensorControl.None ? context.l10n.panoramaEnableSensorControl : context.l10n.panoramaDisableSensorControl,
); );