info: easier access to rating/tag edition
This commit is contained in:
parent
05dc8beec0
commit
1fc9fb040e
10 changed files with 81 additions and 58 deletions
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue