diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f0f2efe4..0bcea8ff5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ All notable changes to this project will be documented in this file. ## [Unreleased] -## [v1.4.2] - 2021-06-10 +## [v1.4.3] - 2021-06-12 ### Added - Collection: snack bar action to show moved/copied/exported entries - Collection / Albums / Countries / Tags: when switching device orientation, keep items in view @@ -23,6 +23,8 @@ All notable changes to this project will be documented in this file. - fixed opening files shared via content URI with incorrect MIME type - refresh collection when entries modified in Viewer no longer match collection filters +## [v1.4.2] - 2021-06-10 [YANKED] + ## [v1.4.1] - 2021-04-29 ### Added - Motion photo support diff --git a/lib/model/actions/entry_actions.dart b/lib/model/actions/entry_actions.dart index f709c0e8d..5a9a5c0be 100644 --- a/lib/model/actions/entry_actions.dart +++ b/lib/model/actions/entry_actions.dart @@ -4,20 +4,24 @@ import 'package:flutter/widgets.dart'; enum EntryAction { delete, - edit, export, - flip, info, - open, - openMap, print, rename, - rotateCCW, - rotateCW, - setAs, share, toggleFavourite, + // raster + rotateCCW, + rotateCW, + flip, + // vector viewSource, + // external + edit, + open, + openMap, + setAs, + // debug debug, } @@ -55,7 +59,6 @@ class EntryActions { extension ExtraEntryAction on EntryAction { String getText(BuildContext context) { switch (this) { - // in app actions case EntryAction.toggleFavourite: // different data depending on toggle state return context.l10n.entryActionAddFavourite; @@ -67,19 +70,21 @@ extension ExtraEntryAction on EntryAction { return context.l10n.entryActionInfo; case EntryAction.rename: return context.l10n.entryActionRename; + case EntryAction.print: + return context.l10n.entryActionPrint; + case EntryAction.share: + return context.l10n.entryActionShare; + // raster case EntryAction.rotateCCW: return context.l10n.entryActionRotateCCW; case EntryAction.rotateCW: return context.l10n.entryActionRotateCW; case EntryAction.flip: return context.l10n.entryActionFlip; - case EntryAction.print: - return context.l10n.entryActionPrint; - case EntryAction.share: - return context.l10n.entryActionShare; + // vector case EntryAction.viewSource: return context.l10n.entryActionViewSource; - // external app actions + // external case EntryAction.edit: return context.l10n.entryActionEdit; case EntryAction.open: @@ -88,6 +93,7 @@ extension ExtraEntryAction on EntryAction { return context.l10n.entryActionSetAs; case EntryAction.openMap: return context.l10n.entryActionOpenMap; + // debug case EntryAction.debug: return 'Debug'; } @@ -95,7 +101,6 @@ extension ExtraEntryAction on EntryAction { IconData? getIcon() { switch (this) { - // in app actions case EntryAction.toggleFavourite: // different data depending on toggle state return AIcons.favourite; @@ -107,24 +112,27 @@ extension ExtraEntryAction on EntryAction { return AIcons.info; case EntryAction.rename: return AIcons.rename; + case EntryAction.print: + return AIcons.print; + case EntryAction.share: + return AIcons.share; + // raster case EntryAction.rotateCCW: return AIcons.rotateLeft; case EntryAction.rotateCW: return AIcons.rotateRight; case EntryAction.flip: return AIcons.flip; - case EntryAction.print: - return AIcons.print; - case EntryAction.share: - return AIcons.share; + // vector case EntryAction.viewSource: return AIcons.vector; - // external app actions + // external case EntryAction.edit: case EntryAction.open: case EntryAction.setAs: case EntryAction.openMap: return null; + // debug case EntryAction.debug: return AIcons.debug; } diff --git a/lib/widgets/common/grid/section_layout.dart b/lib/widgets/common/grid/section_layout.dart index 4d4d60bc3..fa4c8b25f 100644 --- a/lib/widgets/common/grid/section_layout.dart +++ b/lib/widgets/common/grid/section_layout.dart @@ -5,6 +5,7 @@ import 'package:aves/theme/durations.dart'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:provider/provider.dart'; @@ -123,7 +124,8 @@ abstract class SectionedListLayoutProvider extends StatelessWidget { ); children.add(animate ? _buildAnimation(itemGridIndex, item) : item); } - return Wrap( + return _GridRow( + extent: tileExtent, spacing: spacing, children: children, ); @@ -274,3 +276,128 @@ class SectionLayout { @override String toString() => '$runtimeType#${shortHash(this)}{sectionKey=$sectionKey, firstIndex=$firstIndex, lastIndex=$lastIndex, minOffset=$minOffset, maxOffset=$maxOffset, headerExtent=$headerExtent, tileExtent=$tileExtent, spacing=$spacing}'; } + +class _GridRow extends MultiChildRenderObjectWidget { + final double extent, spacing; + + _GridRow({ + Key? key, + required this.extent, + required this.spacing, + required List children, + }) : super(key: key, children: children); + + @override + RenderObject createRenderObject(BuildContext context) { + return _RenderGridRow( + extent: extent, + spacing: spacing, + ); + } + + @override + void updateRenderObject(BuildContext context, _RenderGridRow renderObject) { + renderObject.extent = extent; + renderObject.spacing = spacing; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DoubleProperty('extent', extent)); + properties.add(DoubleProperty('spacing', spacing)); + } +} + +class _GridRowParentData extends ContainerBoxParentData {} + +class _RenderGridRow extends RenderBox with ContainerRenderObjectMixin, RenderBoxContainerDefaultsMixin { + _RenderGridRow({ + List? children, + required double extent, + required double spacing, + }) : _extent = extent, + _spacing = spacing { + addAll(children); + } + + double get extent => _extent; + double _extent; + + set extent(double value) { + if (_extent == value) return; + _extent = value; + markNeedsLayout(); + } + + double get spacing => _spacing; + double _spacing; + + set spacing(double value) { + if (_spacing == value) return; + _spacing = value; + markNeedsLayout(); + } + + @override + void setupParentData(RenderBox child) { + if (child.parentData is! _GridRowParentData) { + child.parentData = _GridRowParentData(); + } + } + + double get intrinsicWidth => extent * childCount + spacing * (childCount - 1); + + @override + double computeMinIntrinsicWidth(double height) => intrinsicWidth; + + @override + double computeMaxIntrinsicWidth(double height) => intrinsicWidth; + + @override + double computeMinIntrinsicHeight(double width) => extent; + + @override + double computeMaxIntrinsicHeight(double width) => extent; + + @override + void performLayout() { + var child = firstChild; + if (child == null) { + size = constraints.smallest; + return; + } + size = Size(constraints.maxWidth, extent); + final childConstraints = BoxConstraints.tight(Size(extent, extent)); + var offset = Offset.zero; + while (child != null) { + child.layout(childConstraints, parentUsesSize: false); + final childParentData = child.parentData! as _GridRowParentData; + childParentData.offset = offset; + offset += Offset(extent + spacing, 0); + child = childParentData.nextSibling; + } + } + + @override + double? computeDistanceToActualBaseline(TextBaseline baseline) { + return defaultComputeDistanceToHighestActualBaseline(baseline); + } + + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + return defaultHitTestChildren(result, position: position); + } + + @override + void paint(PaintingContext context, Offset offset) { + defaultPaint(context, offset); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DoubleProperty('extent', extent)); + properties.add(DoubleProperty('spacing', spacing)); + } +} diff --git a/lib/widgets/settings/entry_actions_editor.dart b/lib/widgets/settings/entry_actions_editor.dart new file mode 100644 index 000000000..c60dba463 --- /dev/null +++ b/lib/widgets/settings/entry_actions_editor.dart @@ -0,0 +1,55 @@ +import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/settings/settings.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/settings/quick_actions/editor_page.dart'; +import 'package:flutter/material.dart'; + +class QuickEntryActionsTile extends StatelessWidget { + @override + Widget build(BuildContext context) { + return ListTile( + title: Text(context.l10n.settingsViewerQuickActionsTile), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + settings: const RouteSettings(name: QuickEntryActionEditorPage.routeName), + builder: (context) => const QuickEntryActionEditorPage(), + ), + ); + }, + ); + } +} + +class QuickEntryActionEditorPage extends StatelessWidget { + static const routeName = '/settings/quick_entry_actions'; + + const QuickEntryActionEditorPage({Key? key}) : super(key: key); + + static const allAvailableActions = [ + EntryAction.info, + EntryAction.toggleFavourite, + EntryAction.share, + EntryAction.delete, + EntryAction.rename, + EntryAction.export, + EntryAction.print, + EntryAction.viewSource, + EntryAction.flip, + EntryAction.rotateCCW, + EntryAction.rotateCW, + ]; + + @override + Widget build(BuildContext context) { + return QuickActionEditorPage( + bannerText: context.l10n.settingsViewerQuickActionEditorBanner, + allAvailableActions: allAvailableActions, + actionIcon: (action) => action.getIcon(), + actionText: (context, action) => action.getText(context), + load: () => settings.viewerQuickActions.toList(), + save: (actions) => settings.viewerQuickActions = actions, + ); + } +} diff --git a/lib/widgets/settings/quick_actions/action_button.dart b/lib/widgets/settings/quick_actions/action_button.dart new file mode 100644 index 000000000..47c0f90d4 --- /dev/null +++ b/lib/widgets/settings/quick_actions/action_button.dart @@ -0,0 +1,48 @@ +import 'package:aves/widgets/viewer/overlay/common.dart'; +import 'package:flutter/material.dart'; + +class ActionButton extends StatelessWidget { + final String text; + final IconData? icon; + final bool enabled, showCaption; + + const ActionButton({ + required this.text, + required this.icon, + this.enabled = true, + this.showCaption = true, + }); + + static const padding = 8.0; + + @override + Widget build(BuildContext context) { + final textStyle = Theme.of(context).textTheme.caption; + return SizedBox( + width: OverlayButton.getSize(context) + padding * 2, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: padding), + OverlayButton( + child: IconButton( + icon: Icon(icon), + onPressed: enabled ? () {} : null, + ), + ), + if (showCaption) ...[ + const SizedBox(height: padding), + Text( + text, + style: enabled ? textStyle : textStyle!.copyWith(color: textStyle.color!.withOpacity(.2)), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: 2, + ), + ], + const SizedBox(height: padding), + ], + ), + ); + } +} diff --git a/lib/widgets/settings/quick_actions/action_panel.dart b/lib/widgets/settings/quick_actions/action_panel.dart new file mode 100644 index 000000000..ba56c2d50 --- /dev/null +++ b/lib/widgets/settings/quick_actions/action_panel.dart @@ -0,0 +1,33 @@ +import 'package:aves/theme/durations.dart'; +import 'package:flutter/material.dart'; + +class ActionPanel extends StatelessWidget { + final bool highlight; + final Widget child; + + const ActionPanel({ + this.highlight = false, + required this.child, + }); + + @override + Widget build(BuildContext context) { + final color = highlight ? Theme.of(context).accentColor : Colors.blueGrey; + return AnimatedContainer( + foregroundDecoration: BoxDecoration( + color: color.withOpacity(.2), + border: Border.fromBorderSide(BorderSide( + color: color, + width: highlight ? 2 : 1, + )), + borderRadius: const BorderRadius.all(Radius.circular(8)), + ), + margin: const EdgeInsets.all(16), + duration: Durations.quickActionHighlightAnimation, + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: child, + ), + ); + } +} diff --git a/lib/widgets/settings/quick_actions/available_actions.dart b/lib/widgets/settings/quick_actions/available_actions.dart index 76ebd1aae..0ec23c24d 100644 --- a/lib/widgets/settings/quick_actions/available_actions.dart +++ b/lib/widgets/settings/quick_actions/available_actions.dart @@ -1,42 +1,33 @@ -import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; -import 'package:aves/widgets/settings/quick_actions/common.dart'; -import 'package:flutter/material.dart'; +import 'package:aves/widgets/settings/quick_actions/action_button.dart'; +import 'package:aves/widgets/settings/quick_actions/placeholder.dart'; +import 'package:flutter/widgets.dart'; -class AvailableActionPanel extends StatelessWidget { - final List quickActions; +class AvailableActionPanel extends StatelessWidget { + final List allActions, quickActions; final Listenable quickActionsChangeNotifier; final ValueNotifier panelHighlight; - final ValueNotifier draggedQuickAction; - final ValueNotifier draggedAvailableAction; - final bool Function(EntryAction? action) removeQuickAction; + final ValueNotifier draggedQuickAction; + final ValueNotifier draggedAvailableAction; + final bool Function(T? action) removeQuickAction; + final IconData? Function(T action) actionIcon; + final String Function(BuildContext context, T action) actionText; const AvailableActionPanel({ + required this.allActions, required this.quickActions, required this.quickActionsChangeNotifier, required this.panelHighlight, required this.draggedQuickAction, required this.draggedAvailableAction, required this.removeQuickAction, + required this.actionIcon, + required this.actionText, }); - static const allActions = [ - EntryAction.info, - EntryAction.toggleFavourite, - EntryAction.share, - EntryAction.delete, - EntryAction.rename, - EntryAction.export, - EntryAction.print, - EntryAction.viewSource, - EntryAction.flip, - EntryAction.rotateCCW, - EntryAction.rotateCW, - ]; - @override Widget build(BuildContext context) { - return DragTarget( + return DragTarget( onWillAccept: (data) { if (draggedQuickAction.value != null) { _setPanelHighlight(true); @@ -61,15 +52,12 @@ class AvailableActionPanel extends StatelessWidget { children: allActions.map((action) { final dragged = action == draggedAvailableAction.value; final enabled = dragged || !quickActions.contains(action); - Widget child = ActionButton( - action: action, - enabled: enabled, - ); + var child = _buildActionButton(context, action, enabled: enabled); if (dragged) { child = DraggedPlaceholder(child: child); } if (enabled) { - child = _buildDraggable(action, child); + child = _buildDraggable(context, action, child); } return child; }).toList(), @@ -80,14 +68,20 @@ class AvailableActionPanel extends StatelessWidget { ); } - Widget _buildDraggable(EntryAction action, Widget child) => LongPressDraggable( + Widget _buildDraggable( + BuildContext context, + T action, + Widget child, + ) => + LongPressDraggable( data: action, maxSimultaneousDrags: 1, onDragStarted: () => _setDraggedAvailableAction(action), onDragEnd: (details) => _setDraggedAvailableAction(null), feedback: MediaQueryDataProvider( - child: ActionButton( - action: action, + child: _buildActionButton( + context, + action, showCaption: false, ), ), @@ -95,9 +89,22 @@ class AvailableActionPanel extends StatelessWidget { child: child, ); - void _setDraggedQuickAction(EntryAction? action) => draggedQuickAction.value = action; + Widget _buildActionButton( + BuildContext context, + T action, { + bool enabled = true, + bool showCaption = true, + }) => + ActionButton( + text: actionText(context, action), + icon: actionIcon(action), + enabled: enabled, + showCaption: showCaption, + ); - void _setDraggedAvailableAction(EntryAction? action) => draggedAvailableAction.value = action; + void _setDraggedQuickAction(T? action) => draggedQuickAction.value = action; + + void _setDraggedAvailableAction(T? action) => draggedAvailableAction.value = action; void _setPanelHighlight(bool flag) => panelHighlight.value = flag; } diff --git a/lib/widgets/settings/quick_actions/common.dart b/lib/widgets/settings/quick_actions/common.dart deleted file mode 100644 index 849091f5f..000000000 --- a/lib/widgets/settings/quick_actions/common.dart +++ /dev/null @@ -1,95 +0,0 @@ -import 'package:aves/model/actions/entry_actions.dart'; -import 'package:aves/theme/durations.dart'; -import 'package:aves/widgets/viewer/overlay/common.dart'; -import 'package:flutter/material.dart'; - -class ActionPanel extends StatelessWidget { - final bool highlight; - final Widget child; - - const ActionPanel({ - this.highlight = false, - required this.child, - }); - - @override - Widget build(BuildContext context) { - final color = highlight ? Theme.of(context).accentColor : Colors.blueGrey; - return AnimatedContainer( - foregroundDecoration: BoxDecoration( - color: color.withOpacity(.2), - border: Border.fromBorderSide(BorderSide( - color: color, - width: highlight ? 2 : 1, - )), - borderRadius: const BorderRadius.all(Radius.circular(8)), - ), - margin: const EdgeInsets.all(16), - duration: Durations.quickActionHighlightAnimation, - child: ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(8)), - child: child, - ), - ); - } -} - -class ActionButton extends StatelessWidget { - final EntryAction action; - final bool enabled, showCaption; - - const ActionButton({ - required this.action, - this.enabled = true, - this.showCaption = true, - }); - - static const padding = 8.0; - - @override - Widget build(BuildContext context) { - final textStyle = Theme.of(context).textTheme.caption; - return SizedBox( - width: OverlayButton.getSize(context) + padding * 2, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox(height: padding), - OverlayButton( - child: IconButton( - icon: Icon(action.getIcon()), - onPressed: enabled ? () {} : null, - ), - ), - if (showCaption) ...[ - const SizedBox(height: padding), - Text( - action.getText(context), - style: enabled ? textStyle : textStyle!.copyWith(color: textStyle.color!.withOpacity(.2)), - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - maxLines: 2, - ), - ], - const SizedBox(height: padding), - ], - ), - ); - } -} - -class DraggedPlaceholder extends StatelessWidget { - final Widget child; - - const DraggedPlaceholder({ - required this.child, - }); - - @override - Widget build(BuildContext context) { - return Opacity( - opacity: .2, - child: child, - ); - } -} diff --git a/lib/widgets/settings/quick_actions/editor.dart b/lib/widgets/settings/quick_actions/editor_page.dart similarity index 82% rename from lib/widgets/settings/quick_actions/editor.dart rename to lib/widgets/settings/quick_actions/editor_page.dart index 151c136ff..4f830ef31 100644 --- a/lib/widgets/settings/quick_actions/editor.dart +++ b/lib/widgets/settings/quick_actions/editor_page.dart @@ -1,51 +1,47 @@ import 'dart:async'; -import 'package:aves/model/actions/entry_actions.dart'; -import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/utils/change_notifier.dart'; import 'package:aves/utils/constants.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; +import 'package:aves/widgets/settings/quick_actions/action_button.dart'; +import 'package:aves/widgets/settings/quick_actions/action_panel.dart'; import 'package:aves/widgets/settings/quick_actions/available_actions.dart'; -import 'package:aves/widgets/settings/quick_actions/common.dart'; +import 'package:aves/widgets/settings/quick_actions/placeholder.dart'; import 'package:aves/widgets/settings/quick_actions/quick_actions.dart'; import 'package:aves/widgets/viewer/overlay/common.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -class QuickActionsTile extends StatelessWidget { - @override - Widget build(BuildContext context) { - return ListTile( - title: Text(context.l10n.settingsViewerQuickActionsTile), - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - settings: const RouteSettings(name: QuickActionEditorPage.routeName), - builder: (context) => QuickActionEditorPage(), - ), - ); - }, - ); - } -} +class QuickActionEditorPage extends StatefulWidget { + final String bannerText; + final List allAvailableActions; + final IconData? Function(T action) actionIcon; + final String Function(BuildContext context, T action) actionText; + final List Function() load; + final void Function(List actions) save; -class QuickActionEditorPage extends StatefulWidget { - static const routeName = '/settings/quick_actions'; + const QuickActionEditorPage({ + required this.bannerText, + required this.allAvailableActions, + required this.actionIcon, + required this.actionText, + required this.load, + required this.save, + }); @override - _QuickActionEditorPageState createState() => _QuickActionEditorPageState(); + _QuickActionEditorPageState createState() => _QuickActionEditorPageState(); } -class _QuickActionEditorPageState extends State { +class _QuickActionEditorPageState extends State> { final GlobalKey _animatedListKey = GlobalKey(debugLabel: 'quick-actions-animated-list'); Timer? _targetLeavingTimer; - late List _quickActions; - final ValueNotifier _draggedQuickAction = ValueNotifier(null); - final ValueNotifier _draggedAvailableAction = ValueNotifier(null); + late List _quickActions; + final ValueNotifier _draggedQuickAction = ValueNotifier(null); + final ValueNotifier _draggedAvailableAction = ValueNotifier(null); final ValueNotifier _quickActionHighlight = ValueNotifier(false); final ValueNotifier _availableActionHighlight = ValueNotifier(false); final AChangeNotifier _quickActionsChangeNotifier = AChangeNotifier(); @@ -59,7 +55,7 @@ class _QuickActionEditorPageState extends State { @override void initState() { super.initState(); - _quickActions = settings.viewerQuickActions.toList(); + _quickActions = widget.load(); } @override @@ -79,7 +75,7 @@ class _QuickActionEditorPageState extends State { @override Widget build(BuildContext context) { - final header = QuickActionButton( + final header = QuickActionButton( placement: QuickActionPlacement.header, panelHighlight: _quickActionHighlight, draggedQuickAction: _draggedQuickAction, @@ -88,7 +84,7 @@ class _QuickActionEditorPageState extends State { removeAction: _removeQuickAction, onTargetLeave: _onQuickActionTargetLeave, ); - final footer = QuickActionButton( + final footer = QuickActionButton( placement: QuickActionPlacement.footer, panelHighlight: _quickActionHighlight, draggedQuickAction: _draggedQuickAction, @@ -104,7 +100,7 @@ class _QuickActionEditorPageState extends State { ), body: WillPopScope( onWillPop: () { - settings.viewerQuickActions = _quickActions; + widget.save(_quickActions); return SynchronousFuture(true); }, child: SafeArea( @@ -116,7 +112,7 @@ class _QuickActionEditorPageState extends State { children: [ const Icon(AIcons.info), const SizedBox(width: 16), - Expanded(child: Text(context.l10n.settingsViewerQuickActionEditorBanner)), + Expanded(child: Text(widget.bannerText)), ], ), ), @@ -163,7 +159,7 @@ class _QuickActionEditorPageState extends State { itemBuilder: (context, index, animation) { if (index >= _quickActions.length) return const SizedBox(); final action = _quickActions[index]; - return QuickActionButton( + return QuickActionButton( placement: QuickActionPlacement.action, action: action, panelHighlight: _quickActionHighlight, @@ -172,6 +168,11 @@ class _QuickActionEditorPageState extends State { insertAction: _insertQuickAction, removeAction: _removeQuickAction, onTargetLeave: _onQuickActionTargetLeave, + draggableFeedbackBuilder: (action) => ActionButton( + text: widget.actionText(context, action), + icon: widget.actionIcon(action), + showCaption: false, + ), child: _buildQuickActionButton(action, animation), ); }, @@ -205,13 +206,16 @@ class _QuickActionEditorPageState extends State { highlight: highlight, child: child!, ), - child: AvailableActionPanel( + child: AvailableActionPanel( + allActions: widget.allAvailableActions, quickActions: _quickActions, quickActionsChangeNotifier: _quickActionsChangeNotifier, panelHighlight: _availableActionHighlight, draggedQuickAction: _draggedQuickAction, draggedAvailableAction: _draggedAvailableAction, removeQuickAction: _removeQuickAction, + actionIcon: widget.actionIcon, + actionText: widget.actionText, ), ), ], @@ -224,7 +228,7 @@ class _QuickActionEditorPageState extends State { void _stopLeavingTimer() => _targetLeavingTimer?.cancel(); - bool _insertQuickAction(EntryAction action, QuickActionPlacement placement, EntryAction? overAction) { + bool _insertQuickAction(T action, QuickActionPlacement placement, T? overAction) { _stopLeavingTimer(); if (_reordering) return false; @@ -256,7 +260,7 @@ class _QuickActionEditorPageState extends State { return true; } - bool _removeQuickAction(EntryAction? action) { + bool _removeQuickAction(T? action) { if (action == null || !_quickActions.contains(action)) return false; final index = _quickActions.indexOf(action); @@ -270,7 +274,7 @@ class _QuickActionEditorPageState extends State { return true; } - Widget _buildQuickActionButton(EntryAction action, Animation animation) { + Widget _buildQuickActionButton(T action, Animation animation) { animation = animation.drive(CurveTween(curve: Curves.easeInOut)); Widget child = FadeTransition( opacity: animation, @@ -281,7 +285,7 @@ class _QuickActionEditorPageState extends State { padding: const EdgeInsets.symmetric(vertical: _QuickActionEditorPageState.quickActionVerticalPadding, horizontal: 4), child: OverlayButton( child: IconButton( - icon: Icon(action.getIcon()), + icon: Icon(widget.actionIcon(action)), onPressed: () {}, ), ), diff --git a/lib/widgets/settings/quick_actions/placeholder.dart b/lib/widgets/settings/quick_actions/placeholder.dart new file mode 100644 index 000000000..b58085644 --- /dev/null +++ b/lib/widgets/settings/quick_actions/placeholder.dart @@ -0,0 +1,17 @@ +import 'package:flutter/widgets.dart'; + +class DraggedPlaceholder extends StatelessWidget { + final Widget child; + + const DraggedPlaceholder({ + required this.child, + }); + + @override + Widget build(BuildContext context) { + return Opacity( + opacity: .2, + child: child, + ); + } +} diff --git a/lib/widgets/settings/quick_actions/quick_actions.dart b/lib/widgets/settings/quick_actions/quick_actions.dart index 9dd10212a..1e8215705 100644 --- a/lib/widgets/settings/quick_actions/quick_actions.dart +++ b/lib/widgets/settings/quick_actions/quick_actions.dart @@ -1,19 +1,18 @@ -import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; -import 'package:aves/widgets/settings/quick_actions/common.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; enum QuickActionPlacement { header, action, footer } -class QuickActionButton extends StatelessWidget { +class QuickActionButton extends StatelessWidget { final QuickActionPlacement placement; - final EntryAction? action; + final T? action; final ValueNotifier panelHighlight; - final ValueNotifier draggedQuickAction; - final ValueNotifier draggedAvailableAction; - final bool Function(EntryAction action, QuickActionPlacement placement, EntryAction? overAction) insertAction; - final bool Function(EntryAction action) removeAction; + final ValueNotifier draggedQuickAction; + final ValueNotifier draggedAvailableAction; + final bool Function(T action, QuickActionPlacement placement, T? overAction) insertAction; + final bool Function(T action) removeAction; final VoidCallback onTargetLeave; + final Widget Function(T action)? draggableFeedbackBuilder; final Widget? child; const QuickActionButton({ @@ -25,6 +24,7 @@ class QuickActionButton extends StatelessWidget { required this.insertAction, required this.removeAction, required this.onTargetLeave, + this.draggableFeedbackBuilder, this.child, }); @@ -38,8 +38,8 @@ class QuickActionButton extends StatelessWidget { return child; } - DragTarget _buildDragTarget(Widget? child) { - return DragTarget( + DragTarget _buildDragTarget(Widget? child) { + return DragTarget( onWillAccept: (data) { if (draggedQuickAction.value != null) { insertAction(draggedQuickAction.value!, placement, action); @@ -56,7 +56,7 @@ class QuickActionButton extends StatelessWidget { ); } - Widget _buildDraggable(Widget child, EntryAction action) => LongPressDraggable( + Widget _buildDraggable(Widget child, T action) => LongPressDraggable( data: action, maxSimultaneousDrags: 1, onDragStarted: () => _setDraggedQuickAction(action), @@ -65,16 +65,13 @@ class QuickActionButton extends StatelessWidget { onDraggableCanceled: (velocity, offset) => _setDraggedQuickAction(null), onDragCompleted: () => _setDraggedQuickAction(null), feedback: MediaQueryDataProvider( - child: ActionButton( - action: action, - showCaption: false, - ), + child: draggableFeedbackBuilder!(action), ), childWhenDragging: child, child: child, ); - void _setDraggedQuickAction(EntryAction? action) => draggedQuickAction.value = action; + void _setDraggedQuickAction(T? action) => draggedQuickAction.value = action; void _setPanelHighlight(bool flag) => panelHighlight.value = flag; } diff --git a/lib/widgets/settings/settings_page.dart b/lib/widgets/settings/settings_page.dart index a8316fcf6..21813c44e 100644 --- a/lib/widgets/settings/settings_page.dart +++ b/lib/widgets/settings/settings_page.dart @@ -19,7 +19,7 @@ import 'package:aves/widgets/settings/access_grants.dart'; import 'package:aves/widgets/settings/entry_background.dart'; import 'package:aves/widgets/settings/hidden_filters.dart'; import 'package:aves/widgets/settings/language.dart'; -import 'package:aves/widgets/settings/quick_actions/editor.dart'; +import 'package:aves/widgets/settings/entry_actions_editor.dart'; import 'package:decorated_icon/decorated_icon.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; @@ -191,7 +191,7 @@ class _SettingsPageState extends State { expandedNotifier: _expandedNotifier, showHighlight: false, children: [ - QuickActionsTile(), + QuickEntryActionsTile(), SwitchListTile( value: settings.showOverlayMinimap, onChanged: (v) => settings.showOverlayMinimap = v, diff --git a/pubspec.yaml b/pubspec.yaml index af2ebc7d4..3a139cea4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: aves description: A visual media gallery and metadata explorer app. repository: https://github.com/deckerst/aves -version: 1.4.2+46 +version: 1.4.3+47 publish_to: none environment: diff --git a/whatsnew/whatsnew-en-US b/whatsnew/whatsnew-en-US index ba1f34c79..ffb52b9db 100644 --- a/whatsnew/whatsnew-en-US +++ b/whatsnew/whatsnew-en-US @@ -1,5 +1,5 @@ Thanks for using Aves! -v1.4.2: +v1.4.3: - improved navigation usability - changed thumbnail layout - improved playing videos with non-square pixels