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:
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

View file

@ -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;
}

View file

@ -1,9 +1,13 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
@immutable
class ActionEvent<T> {
class ActionEvent<T> extends Equatable {
final T action;
@override
List<Object?> get props => [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

View file

@ -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;

View file

@ -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;

View file

@ -122,7 +122,7 @@ class _TagEditorPageState extends State<TagEditorPage> {
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,

View file

@ -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<bool> isEditingTagNotifier;
final ValueNotifier<EntryInfoAction?> 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 = <Widget>[
...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<bool>(
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<EntryInfoAction?>(
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,
),
),
),
),
],
);
},

View file

@ -150,7 +150,7 @@ class _InfoPageContentState extends State<_InfoPageContent> {
final List<StreamSubscription> _subscriptions = [];
late EntryInfoActionDelegate _actionDelegate;
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);
@ -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<EntryInfoAction> 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) {

View file

@ -109,7 +109,7 @@ class _PanoramaPageState extends State<PanoramaPage> {
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,
);