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:
|
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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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,
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue