aves/lib/widgets/settings/common/quick_actions/available_actions.dart

136 lines
4.9 KiB
Dart

import 'package:aves/model/settings/settings.dart';
import 'package:aves/widgets/common/identity/buttons/captioned_button.dart';
import 'package:aves/widgets/common/identity/buttons/overlay_button.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/settings/common/quick_actions/placeholder.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class AvailableActionPanel<T extends Object> extends StatelessWidget {
final List<T> allActions, quickActions;
final Listenable quickActionsChangeNotifier;
final ValueNotifier<bool> panelHighlight;
final ValueNotifier<T?> draggedQuickAction;
final ValueNotifier<T?> draggedAvailableAction;
final bool Function(T? action) removeQuickAction;
final Widget Function(T action) actionIcon;
final String Function(BuildContext context, T action) actionText;
static const double spacing = 8;
static const double runSpacing = 20;
static const padding = EdgeInsets.symmetric(vertical: 16, horizontal: 8);
const AvailableActionPanel({
super.key,
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,
});
@override
Widget build(BuildContext context) {
return DragTarget<T>(
onWillAcceptWithDetails: (details) {
if (draggedQuickAction.value != null) {
_setPanelHighlight(true);
}
return true;
},
onAcceptWithDetails: (details) {
removeQuickAction(draggedQuickAction.value);
_setDraggedQuickAction(null);
_setPanelHighlight(false);
},
onLeave: (data) => _setPanelHighlight(false),
builder: (context, accepted, rejected) {
return AnimatedBuilder(
animation: Listenable.merge([quickActionsChangeNotifier, draggedAvailableAction]),
builder: (context, child) {
return Padding(
padding: padding,
child: Wrap(
alignment: WrapAlignment.spaceEvenly,
spacing: spacing,
runSpacing: runSpacing,
children: allActions.map((action) {
final dragged = action == draggedAvailableAction.value;
final enabled = dragged || !quickActions.contains(action);
var child = _buildActionButton(context, action, enabled: enabled);
if (dragged) {
child = DraggedPlaceholder(child: child);
}
if (enabled) {
child = _buildDraggable(context, action, child);
}
return child;
}).toList(),
),
);
},
);
},
);
}
Widget _buildDraggable(
BuildContext context,
T action,
Widget child,
) =>
LongPressDraggable<T>(
feedback: MediaQueryDataProvider(
child: _buildActionButton(
context,
action,
showCaption: false,
),
),
data: action,
dragAnchorStrategy: (draggable, context, position) {
return childDragAnchorStrategy(draggable, context, position) + Offset(0, OverlayButton.getSize(context));
},
maxSimultaneousDrags: 1,
onDragStarted: () => _setDraggedAvailableAction(action),
onDragEnd: (details) => _setDraggedAvailableAction(null),
delay: settings.longPressTimeout,
childWhenDragging: child,
child: child,
);
Widget _buildActionButton(
BuildContext context,
T action, {
bool enabled = true,
bool showCaption = true,
}) =>
CaptionedButton(
icon: actionIcon(action),
caption: actionText(context, action),
showCaption: showCaption,
onPressed: enabled ? () {} : null,
);
void _setDraggedQuickAction(T? action) => draggedQuickAction.value = action;
void _setDraggedAvailableAction(T? action) => draggedAvailableAction.value = action;
void _setPanelHighlight(bool flag) => panelHighlight.value = flag;
static double heightFor(BuildContext context, List<String> captions, double width) {
final buttonSizes = captions.map((v) => CaptionedButton.getSize(context, v, showCaption: true));
final actionsPerRun = (width - padding.horizontal + spacing) ~/ (buttonSizes.first.width + spacing);
final runCount = (captions.length / actionsPerRun).ceil();
var height = runSpacing * (runCount - 1) + padding.vertical / 2;
for (var i = 0; i < runCount; i++) {
height += buttonSizes.skip(i * actionsPerRun).take(actionsPerRun).map((v) => v.height).max;
}
return height;
}
}