import 'package:flutter/material.dart'; import '../../state/game_session_notifier.dart'; import 'board_painter.dart'; /// The board with clear-flash and combo-text effects. Pure display: drag /// orchestration lives in the game screen. class BoardWidget extends StatefulWidget { const BoardWidget({super.key, required this.view, required this.ghost}); final GameViewState view; final GhostSpec? ghost; @override State createState() => _BoardWidgetState(); } class _BoardWidgetState extends State with SingleTickerProviderStateMixin { late final AnimationController _flash = AnimationController( vsync: this, duration: const Duration(milliseconds: 350), ); List _flashRows = const []; List _flashCols = const []; int _comboStreak = 0; @override void didUpdateWidget(BoardWidget old) { super.didUpdateWidget(old); final placement = widget.view.lastPlacement; if (widget.view.fxTick != old.view.fxTick && placement != null && placement.linesCleared > 0) { _flashRows = placement.clearedRows; _flashCols = placement.clearedCols; _comboStreak = placement.comboStreak; _flash.forward(from: 0); } } @override void dispose() { _flash.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AspectRatio( aspectRatio: 1, child: AnimatedBuilder( animation: _flash, builder: (context, _) { final fading = _flash.isAnimating ? 1 - _flash.value : 0.0; return Stack( fit: StackFit.expand, children: [ CustomPaint( painter: BoardPainter( grid: widget.view.grid, ghost: widget.ghost, flashProgress: fading, flashRows: _flashRows, flashCols: _flashCols, ), ), if (_flash.isAnimating && _comboStreak >= 2) Center( child: Transform.scale( scale: 0.8 + 0.6 * _flash.value, child: Opacity( opacity: 1 - _flash.value, child: Text( 'COMBO x$_comboStreak', style: TextStyle( fontSize: 36, fontWeight: FontWeight.w900, color: Colors.amber.shade300, shadows: const [ Shadow(blurRadius: 12, color: Colors.black54), ], ), ), ), ), ), ], ); }, ), ); } }