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,
    );
  }
}