mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-21 06:52:18 +02:00
The Material theme now draws its main surfaces with rounded Material Symbols: navigation, player, mini player, queue, settings, sheets, dialogs and shared controls. AppIcon maps legacy Icons to their symbol and keeps outlined glyphs unfilled. Mornye keeps its original icons. Icon buttons, text and filled buttons, dialog actions, sliders, the setup FAB and indeterminate spinners use the Material 3 Expressive components. Each wrapper falls back to the standard Flutter widget for Mornye, reduced motion and custom sizing, so existing layouts and hit areas stay intact.
204 lines
6.0 KiB
Dart
204 lines
6.0 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:material_3_expressive/material_3_expressive.dart';
|
|
import 'package:spotiflac_android/theme/material_expressive.dart';
|
|
import 'package:spotiflac_android/widgets/app_icon.dart';
|
|
|
|
enum _AppIconButtonVariant { standard, filled, tonal, outlined }
|
|
|
|
/// Drop-in [IconButton] with the Material 3 Expressive shape and press motion.
|
|
///
|
|
/// Mornye, reduced motion and dense buttons ([padding], [constraints],
|
|
/// [visualDensity] or a sizing [style]) keep the exact [IconButton] layout.
|
|
class AppIconButton extends StatelessWidget {
|
|
const AppIconButton({
|
|
super.key,
|
|
required this.icon,
|
|
required this.onPressed,
|
|
this.tooltip,
|
|
this.color,
|
|
this.iconSize,
|
|
this.isSelected,
|
|
this.selectedIcon,
|
|
this.style,
|
|
this.padding,
|
|
this.constraints,
|
|
this.visualDensity,
|
|
}) : _variant = _AppIconButtonVariant.standard;
|
|
|
|
const AppIconButton.filled({
|
|
super.key,
|
|
required this.icon,
|
|
required this.onPressed,
|
|
this.tooltip,
|
|
this.color,
|
|
this.iconSize,
|
|
this.isSelected,
|
|
this.selectedIcon,
|
|
this.style,
|
|
this.padding,
|
|
this.constraints,
|
|
this.visualDensity,
|
|
}) : _variant = _AppIconButtonVariant.filled;
|
|
|
|
const AppIconButton.filledTonal({
|
|
super.key,
|
|
required this.icon,
|
|
required this.onPressed,
|
|
this.tooltip,
|
|
this.color,
|
|
this.iconSize,
|
|
this.isSelected,
|
|
this.selectedIcon,
|
|
this.style,
|
|
this.padding,
|
|
this.constraints,
|
|
this.visualDensity,
|
|
}) : _variant = _AppIconButtonVariant.tonal;
|
|
|
|
const AppIconButton.outlined({
|
|
super.key,
|
|
required this.icon,
|
|
required this.onPressed,
|
|
this.tooltip,
|
|
this.color,
|
|
this.iconSize,
|
|
this.isSelected,
|
|
this.selectedIcon,
|
|
this.style,
|
|
this.padding,
|
|
this.constraints,
|
|
this.visualDensity,
|
|
}) : _variant = _AppIconButtonVariant.outlined;
|
|
|
|
final Widget icon;
|
|
final VoidCallback? onPressed;
|
|
final String? tooltip;
|
|
final Color? color;
|
|
final double? iconSize;
|
|
final bool? isSelected;
|
|
final Widget? selectedIcon;
|
|
final ButtonStyle? style;
|
|
final EdgeInsetsGeometry? padding;
|
|
final BoxConstraints? constraints;
|
|
final VisualDensity? visualDensity;
|
|
final _AppIconButtonVariant _variant;
|
|
|
|
bool get _keepsMaterialLayout {
|
|
final style = this.style;
|
|
return padding != null ||
|
|
constraints != null ||
|
|
visualDensity != null ||
|
|
style != null &&
|
|
(style.padding != null ||
|
|
style.minimumSize != null ||
|
|
style.fixedSize != null ||
|
|
style.maximumSize != null ||
|
|
style.visualDensity != null ||
|
|
style.tapTargetSize != null ||
|
|
style.shape != null ||
|
|
style.iconSize != null);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final icon = symbolIconWidget(this.icon);
|
|
final selectedIcon = this.selectedIcon == null
|
|
? null
|
|
: symbolIconWidget(this.selectedIcon!);
|
|
if (!materialExpressiveEnabled(context) || _keepsMaterialLayout) {
|
|
return switch (_variant) {
|
|
_AppIconButtonVariant.standard => IconButton(
|
|
icon: icon,
|
|
selectedIcon: selectedIcon,
|
|
onPressed: onPressed,
|
|
tooltip: tooltip,
|
|
color: color,
|
|
iconSize: iconSize,
|
|
isSelected: isSelected,
|
|
style: style,
|
|
padding: padding,
|
|
constraints: constraints,
|
|
visualDensity: visualDensity,
|
|
),
|
|
_AppIconButtonVariant.filled => IconButton.filled(
|
|
icon: icon,
|
|
selectedIcon: selectedIcon,
|
|
onPressed: onPressed,
|
|
tooltip: tooltip,
|
|
color: color,
|
|
iconSize: iconSize,
|
|
isSelected: isSelected,
|
|
style: style,
|
|
padding: padding,
|
|
constraints: constraints,
|
|
visualDensity: visualDensity,
|
|
),
|
|
_AppIconButtonVariant.tonal => IconButton.filledTonal(
|
|
icon: icon,
|
|
selectedIcon: selectedIcon,
|
|
onPressed: onPressed,
|
|
tooltip: tooltip,
|
|
color: color,
|
|
iconSize: iconSize,
|
|
isSelected: isSelected,
|
|
style: style,
|
|
padding: padding,
|
|
constraints: constraints,
|
|
visualDensity: visualDensity,
|
|
),
|
|
_AppIconButtonVariant.outlined => IconButton.outlined(
|
|
icon: icon,
|
|
selectedIcon: selectedIcon,
|
|
onPressed: onPressed,
|
|
tooltip: tooltip,
|
|
color: color,
|
|
iconSize: iconSize,
|
|
isSelected: isSelected,
|
|
style: style,
|
|
padding: padding,
|
|
constraints: constraints,
|
|
visualDensity: visualDensity,
|
|
),
|
|
};
|
|
}
|
|
final enabledColor = color;
|
|
final foreground =
|
|
style?.foregroundColor ??
|
|
(enabledColor == null
|
|
? null
|
|
: WidgetStateProperty.resolveWith<Color?>(
|
|
(states) =>
|
|
states.contains(WidgetState.disabled) ? null : enabledColor,
|
|
));
|
|
Widget sized(Widget icon) => iconSize == null
|
|
? icon
|
|
: IconTheme.merge(
|
|
data: IconThemeData(size: iconSize),
|
|
child: icon,
|
|
);
|
|
final button = MaterialExpressiveScope(
|
|
child: M3EIconButton(
|
|
icon: sized(icon),
|
|
selectedIcon: selectedIcon == null ? null : sized(selectedIcon),
|
|
semanticLabel: tooltip,
|
|
onPressed: onPressed,
|
|
isSelected: isSelected,
|
|
variant: switch (_variant) {
|
|
_AppIconButtonVariant.standard => M3EIconButtonVariant.standard,
|
|
_AppIconButtonVariant.filled => M3EIconButtonVariant.filled,
|
|
_AppIconButtonVariant.tonal => M3EIconButtonVariant.tonal,
|
|
_AppIconButtonVariant.outlined => M3EIconButtonVariant.outlined,
|
|
},
|
|
decoration: M3EIconButtonDecoration(
|
|
foregroundColor: foreground,
|
|
backgroundColor: style?.backgroundColor,
|
|
overlayColor: style?.overlayColor,
|
|
side: style?.side,
|
|
),
|
|
),
|
|
);
|
|
final message = tooltip;
|
|
return message == null ? button : Tooltip(message: message, child: button);
|
|
}
|
|
}
|