diff --git a/lib/model/actions/entry_info_actions.dart b/lib/model/actions/entry_info_actions.dart index 94ff10857..ffc6ca198 100644 --- a/lib/model/actions/entry_info_actions.dart +++ b/lib/model/actions/entry_info_actions.dart @@ -50,9 +50,9 @@ extension ExtraEntryInfoAction on EntryInfoAction { case EntryInfoAction.editDate: return AIcons.date; case EntryInfoAction.editRating: - return AIcons.rating; + return AIcons.editRating; case EntryInfoAction.editTags: - return AIcons.addTag; + return AIcons.editTags; case EntryInfoAction.removeMetadata: return AIcons.clear; // motion photo diff --git a/lib/model/actions/entry_set_actions.dart b/lib/model/actions/entry_set_actions.dart index 5b7243b72..f3601560b 100644 --- a/lib/model/actions/entry_set_actions.dart +++ b/lib/model/actions/entry_set_actions.dart @@ -167,9 +167,9 @@ extension ExtraEntrySetAction on EntrySetAction { case EntrySetAction.editDate: return AIcons.date; case EntrySetAction.editRating: - return AIcons.rating; + return AIcons.editRating; case EntrySetAction.editTags: - return AIcons.addTag; + return AIcons.editTags; case EntrySetAction.removeMetadata: return AIcons.clear; } diff --git a/lib/model/actions/events.dart b/lib/model/actions/events.dart index 248e9dd88..e6fea841f 100644 --- a/lib/model/actions/events.dart +++ b/lib/model/actions/events.dart @@ -1,9 +1,13 @@ +import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; @immutable -class ActionEvent { +class ActionEvent extends Equatable { final T action; + @override + List get props => [action]; + const ActionEvent(this.action); } diff --git a/lib/model/filters/location.dart b/lib/model/filters/location.dart index 235475160..6d09d3e21 100644 --- a/lib/model/filters/location.dart +++ b/lib/model/filters/location.dart @@ -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 diff --git a/lib/model/filters/tag.dart b/lib/model/filters/tag.dart index cda7ef146..1ec3b2a4d 100644 --- a/lib/model/filters/tag.dart +++ b/lib/model/filters/tag.dart @@ -44,7 +44,7 @@ class TagFilter extends CollectionFilter { String getLabel(BuildContext context) => tag.isEmpty ? context.l10n.filterTagEmptyLabel : tag; @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 String get category => type; diff --git a/lib/theme/icons.dart b/lib/theme/icons.dart index 2299830f7..c91a307b7 100644 --- a/lib/theme/icons.dart +++ b/lib/theme/icons.dart @@ -19,7 +19,7 @@ class AIcons { static const IconData home = Icons.home_outlined; static const IconData language = Icons.translate_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 privacy = MdiIcons.shieldAccountOutline; 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 shooting = Icons.camera_outlined; static const IconData removableStorage = Icons.sd_storage_outlined; - static const IconData sensorControl = Icons.explore_outlined; - static const IconData sensorControlOff = Icons.explore_off_outlined; + static const IconData sensorControlEnabled = Icons.explore_outlined; + static const IconData sensorControlDisabled = Icons.explore_off_outlined; static const IconData settings = Icons.settings_outlined; static const IconData text = Icons.format_quote_outlined; static const IconData tag = Icons.local_offer_outlined; - static const IconData tagOff = MdiIcons.tagOffOutline; + static const IconData tagUntagged = MdiIcons.tagOffOutline; // view static const IconData group = Icons.group_work_outlined; @@ -44,7 +44,6 @@ class AIcons { // actions static const IconData add = Icons.add_circle_outline; 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 replay10 = Icons.replay_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 delete = Icons.delete_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 flip = Icons.flip_outlined; static const IconData favourite = Icons.favorite_border; diff --git a/lib/widgets/dialogs/entry_editors/edit_entry_tags_dialog.dart b/lib/widgets/dialogs/entry_editors/edit_entry_tags_dialog.dart index cebab3027..d3f7ea85e 100644 --- a/lib/widgets/dialogs/entry_editors/edit_entry_tags_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/edit_entry_tags_dialog.dart @@ -122,7 +122,7 @@ class _TagEditorPageState extends State { child: Row( mainAxisSize: MainAxisSize.min, children: [ - const Icon(AIcons.tagOff, color: untaggedColor), + const Icon(AIcons.tagUntagged, color: untaggedColor), const SizedBox(width: 8), Text( l10n.filterTagEmptyLabel, diff --git a/lib/widgets/viewer/info/basic_section.dart b/lib/widgets/viewer/info/basic_section.dart index d8c414987..70556e422 100644 --- a/lib/widgets/viewer/info/basic_section.dart +++ b/lib/widgets/viewer/info/basic_section.dart @@ -9,7 +9,6 @@ import 'package:aves/model/filters/tag.dart'; import 'package:aves/model/filters/type.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/theme/format.dart'; -import 'package:aves/theme/icons.dart'; import 'package:aves/utils/file_utils.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; @@ -24,7 +23,7 @@ class BasicSection extends StatelessWidget { final AvesEntry entry; final CollectionLens? collection; final EntryInfoActionDelegate actionDelegate; - final ValueNotifier isEditingTagNotifier; + final ValueNotifier isEditingMetadataNotifier; final FilterCallback onFilter; const BasicSection({ @@ -32,7 +31,7 @@ class BasicSection extends StatelessWidget { required this.entry, this.collection, required this.actionDelegate, - required this.isEditingTagNotifier, + required this.isEditingMetadataNotifier, required this.onFilter, }) : super(key: key); @@ -77,6 +76,7 @@ class BasicSection extends StatelessWidget { ), OwnerProp(entry: entry), _buildChips(context), + _buildEditButtons(context), ], ); }); @@ -106,58 +106,78 @@ class BasicSection extends StatelessWidget { if (entry.isFavourite) FavouriteFilter.instance, ]..sort(); - final children = [ - ...effectiveFilters.map((filter) => AvesFilterChip( - filter: filter, - onTap: onFilter, - )), - if (actionDelegate.canApply(EntryInfoAction.editTags)) _buildEditTagButton(context), - ]; - - return children.isEmpty - ? const SizedBox() - : Padding( - padding: const EdgeInsets.symmetric(horizontal: AvesFilterChip.outlineWidth / 2) + const EdgeInsets.only(top: 8), - child: Wrap( - spacing: 8, - runSpacing: 8, - children: children, - ), - ); + return Padding( + padding: const EdgeInsets.symmetric(horizontal: AvesFilterChip.outlineWidth / 2) + const EdgeInsets.only(top: 8), + child: Wrap( + spacing: 8, + runSpacing: 8, + children: effectiveFilters + .map((filter) => AvesFilterChip( + filter: filter, + onTap: onFilter, + )) + .toList(), + ), + ); }, ); } - Widget _buildEditTagButton(BuildContext context) { - const action = EntryInfoAction.editTags; - return ValueListenableBuilder( - valueListenable: isEditingTagNotifier, - builder: (context, isEditing, child) { + Widget _buildEditButtons(BuildContext context) { + final children = [ + EntryInfoAction.editRating, + EntryInfoAction.editTags, + ].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( + valueListenable: isEditingMetadataNotifier, + builder: (context, editingAction, child) { + final isEditing = editingAction != null; return Stack( children: [ DecoratedBox( - decoration: const BoxDecoration( + decoration: BoxDecoration( border: Border.fromBorderSide(BorderSide( - color: AvesFilterChip.defaultOutlineColor, + color: isEditing ? Theme.of(context).disabledColor : AvesFilterChip.defaultOutlineColor, width: AvesFilterChip.outlineWidth, )), - borderRadius: BorderRadius.all(Radius.circular(AvesFilterChip.defaultRadius)), + borderRadius: const BorderRadius.all(Radius.circular(AvesFilterChip.defaultRadius)), ), child: IconButton( - icon: const Icon(AIcons.addTag), + icon: action.getIcon(), onPressed: isEditing ? null : () => actionDelegate.onActionSelected(context, action), tooltip: action.getText(context), ), ), - if (isEditing) - const Positioned.fill( - child: Padding( + Positioned.fill( + child: Visibility( + visible: editingAction == action, + child: const Padding( padding: EdgeInsets.all(1.0), child: CircularProgressIndicator( strokeWidth: AvesFilterChip.outlineWidth, ), ), ), + ), ], ); }, diff --git a/lib/widgets/viewer/info/info_page.dart b/lib/widgets/viewer/info/info_page.dart index 9ea65c17e..ad14c8ee6 100644 --- a/lib/widgets/viewer/info/info_page.dart +++ b/lib/widgets/viewer/info/info_page.dart @@ -150,7 +150,7 @@ class _InfoPageContentState extends State<_InfoPageContent> { final List _subscriptions = []; late EntryInfoActionDelegate _actionDelegate; final ValueNotifier> _metadataNotifier = ValueNotifier({}); - final ValueNotifier _isEditingTagNotifier = ValueNotifier(false); + final ValueNotifier _isEditingMetadataNotifier = ValueNotifier(null); static const horizontalPadding = EdgeInsets.symmetric(horizontal: 8); @@ -197,7 +197,7 @@ class _InfoPageContentState extends State<_InfoPageContent> { entry: entry, collection: collection, actionDelegate: _actionDelegate, - isEditingTagNotifier: _isEditingTagNotifier, + isEditingMetadataNotifier: _isEditingMetadataNotifier, onFilter: _goToCollection, ); final locationAtTop = widget.split && entry.hasGps; @@ -255,15 +255,13 @@ class _InfoPageContentState extends State<_InfoPageContent> { } void _onActionDelegateEvent(ActionEvent event) { - if (event.action == EntryInfoAction.editTags) { - Future.delayed(Durations.dialogTransitionAnimation).then((_) { - if (event is ActionStartedEvent) { - _isEditingTagNotifier.value = true; - } else if (event is ActionEndedEvent) { - _isEditingTagNotifier.value = false; - } - }); - } + Future.delayed(Durations.dialogTransitionAnimation).then((_) { + if (event is ActionStartedEvent) { + _isEditingMetadataNotifier.value = event.action; + } else if (event is ActionEndedEvent) { + _isEditingMetadataNotifier.value = null; + } + }); } void _goToCollection(CollectionFilter filter) { diff --git a/lib/widgets/viewer/panorama_page.dart b/lib/widgets/viewer/panorama_page.dart index 5cdaa4dd1..2ca1be7a2 100644 --- a/lib/widgets/viewer/panorama_page.dart +++ b/lib/widgets/viewer/panorama_page.dart @@ -109,7 +109,7 @@ class _PanoramaPageState extends State { valueListenable: _sensorControl, builder: (context, sensorControl, child) { return IconButton( - icon: Icon(sensorControl == SensorControl.None ? AIcons.sensorControl : AIcons.sensorControlOff), + icon: Icon(sensorControl == SensorControl.None ? AIcons.sensorControlEnabled : AIcons.sensorControlDisabled), onPressed: _toggleSensor, tooltip: sensorControl == SensorControl.None ? context.l10n.panoramaEnableSensorControl : context.l10n.panoramaDisableSensorControl, );