File size: 2,712 Bytes
2992997 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | import 'package:flutter/material.dart';
import '../app_theme.dart';
/// Neo-brutal button. Black 2px border, hard 4px black offset shadow,
/// collapses to (0,0) shadow on press while the body translates (4,4) —
/// matches the web `active:translate-x-[1px] translate-y-[1px]` behavior
/// scaled up for touch.
class BrutalButton extends StatefulWidget {
const BrutalButton({
super.key,
required this.onPressed,
required this.child,
this.color = kBrutalTeal,
this.foregroundColor = kBrutalWhite,
this.padding = const EdgeInsets.symmetric(horizontal: 18, vertical: 14),
this.radius = kBrutalRadius,
this.expand = false,
this.enabled = true,
});
final VoidCallback? onPressed;
final Widget child;
final Color color;
final Color foregroundColor;
final EdgeInsetsGeometry padding;
final double radius;
final bool expand;
final bool enabled;
@override
State<BrutalButton> createState() => _BrutalButtonState();
}
class _BrutalButtonState extends State<BrutalButton> {
bool _pressed = false;
void _setPressed(bool v) {
if (!widget.enabled) return;
if (_pressed == v) return;
setState(() => _pressed = v);
}
@override
Widget build(BuildContext context) {
final disabled = !widget.enabled || widget.onPressed == null;
final offset = _pressed ? 0.0 : 4.0;
final btn = AnimatedContainer(
duration: const Duration(milliseconds: 80),
curve: Curves.easeOut,
padding: widget.padding,
transform: Matrix4.translationValues(_pressed ? 4 : 0, _pressed ? 4 : 0, 0),
decoration: BoxDecoration(
color: disabled ? kBrutalWhite : widget.color,
borderRadius: BorderRadius.circular(widget.radius),
border: Border.all(color: kBrutalBlack, width: 2),
boxShadow: [
BoxShadow(
color: kBrutalBlack,
offset: Offset(offset, offset),
blurRadius: 0,
),
],
),
child: DefaultTextStyle.merge(
style: brutalFont(
color: disabled ? kBrutalMuted : widget.foregroundColor,
fontWeight: FontWeight.w900,
fontSize: 15,
),
textAlign: TextAlign.center,
child: IconTheme.merge(
data: IconThemeData(color: disabled ? kBrutalMuted : widget.foregroundColor, size: 20),
child: widget.child,
),
),
);
return GestureDetector(
onTapDown: (_) => _setPressed(true),
onTapUp: (_) => _setPressed(false),
onTapCancel: () => _setPressed(false),
onTap: disabled ? null : widget.onPressed,
child: widget.expand
? SizedBox(width: double.infinity, child: btn)
: btn,
);
}
}
|