mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-21 23:11:45 +02:00
132 lines
4.0 KiB
Dart
132 lines
4.0 KiB
Dart
import 'dart:math' as math;
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:material_3_expressive/material_3_expressive.dart';
|
|
import 'package:spotiflac_android/theme/app_motion.dart';
|
|
import 'package:spotiflac_android/theme/material_expressive.dart';
|
|
|
|
/// A row of header actions laid out as a Material 3 Expressive button group.
|
|
///
|
|
/// [flexible] actions share the free width; the others keep a [slotWidth]
|
|
/// box. Pressing an action widens it while its neighbours give way. Without
|
|
/// expressive motion the row keeps the centred, content-sized layout with
|
|
/// [Flexible] actions that it replaces.
|
|
class ExpressiveButtonGroup extends StatefulWidget {
|
|
const ExpressiveButtonGroup({
|
|
super.key,
|
|
required this.children,
|
|
this.flexible = const {},
|
|
this.spacing = 12,
|
|
this.slotWidth = 48,
|
|
});
|
|
|
|
final List<Widget> children;
|
|
|
|
/// Indices of [children] that fill the remaining width.
|
|
final Set<int> flexible;
|
|
final double spacing;
|
|
final double slotWidth;
|
|
|
|
@override
|
|
State<ExpressiveButtonGroup> createState() => _ExpressiveButtonGroupState();
|
|
}
|
|
|
|
/// Width an [ExpressiveButtonGroup] gives a fixed action, so an icon button
|
|
/// can stretch its container along with the squish.
|
|
class ExpressiveButtonGroupSlot extends InheritedWidget {
|
|
const ExpressiveButtonGroupSlot({
|
|
super.key,
|
|
required this.width,
|
|
required super.child,
|
|
});
|
|
|
|
final double width;
|
|
|
|
static double? widthOf(BuildContext context) => context
|
|
.dependOnInheritedWidgetOfExactType<ExpressiveButtonGroupSlot>()
|
|
?.width;
|
|
|
|
@override
|
|
bool updateShouldNotify(ExpressiveButtonGroupSlot oldWidget) =>
|
|
width != oldWidget.width;
|
|
}
|
|
|
|
class _ExpressiveButtonGroupState extends State<ExpressiveButtonGroup> {
|
|
static const _squishRatio = 0.15;
|
|
static final _pressSpring = SpringCurve(M3EMotion.expressiveSpatialPress);
|
|
|
|
int? _pressed;
|
|
|
|
void _press(int? index) {
|
|
if (_pressed != index) setState(() => _pressed = index);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final children = widget.children;
|
|
final expressive = materialExpressiveEnabled(context);
|
|
return Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
for (var i = 0; i < children.length; i++) ...[
|
|
if (i > 0) SizedBox(width: widget.spacing),
|
|
if (!expressive)
|
|
widget.flexible.contains(i)
|
|
? Flexible(child: children[i])
|
|
: children[i]
|
|
else
|
|
_slot(i),
|
|
],
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _slot(int index) {
|
|
final pressed = _pressed;
|
|
final neighbour = pressed != null && (index - pressed).abs() == 1;
|
|
final child = Listener(
|
|
onPointerDown: (_) => _press(index),
|
|
onPointerUp: (_) => _press(null),
|
|
onPointerCancel: (_) => _press(null),
|
|
child: widget.children[index],
|
|
);
|
|
if (widget.flexible.contains(index)) {
|
|
// A flexible action grows into whatever its fixed neighbours yield; it
|
|
// only trades weight with another flexible action.
|
|
final weight = pressed == index
|
|
? 1 + _squishRatio
|
|
: neighbour && widget.flexible.contains(pressed)
|
|
? 1 - _squishRatio
|
|
: 1.0;
|
|
return TweenAnimationBuilder<double>(
|
|
tween: Tween(end: weight),
|
|
duration: _pressSpring.duration,
|
|
curve: _pressSpring,
|
|
builder: (context, weight, child) =>
|
|
Expanded(flex: math.max(1, (weight * 1000).round()), child: child!),
|
|
child: child,
|
|
);
|
|
}
|
|
final width =
|
|
widget.slotWidth *
|
|
(pressed == index
|
|
? 1 + _squishRatio
|
|
: neighbour
|
|
? 1 - _squishRatio
|
|
: 1.0);
|
|
return TweenAnimationBuilder<double>(
|
|
tween: Tween(end: width),
|
|
duration: _pressSpring.duration,
|
|
curve: _pressSpring,
|
|
builder: (context, width, child) {
|
|
final slot = math.max(0.0, width);
|
|
return SizedBox(
|
|
width: slot,
|
|
child: ExpressiveButtonGroupSlot(width: slot, child: child!),
|
|
);
|
|
},
|
|
child: child,
|
|
);
|
|
}
|
|
}
|