import 'dart:async'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'gradient_star_badge.dart'; class D10DiceRollerWidget extends StatefulWidget { final String digits; // Current 0..3 digits string, e.g. "7", "74", "742" or "" final bool isRolling; final VoidCallback onRollPressed; final ValueChanged onRollCompleted; const D10DiceRollerWidget({ super.key, required this.digits, required this.isRolling, required this.onRollPressed, required this.onRollCompleted, }); @override State createState() => D10DiceRollerWidgetState(); } class D10DiceRollerWidgetState extends State { final _random = Random(); Timer? _rollTimer; int? _animatedDigit0; int? _animatedDigit1; int? _animatedDigit2; bool _internalRolling = false; void triggerRollAnimation() { if (_internalRolling) return; setState(() { _internalRolling = true; _animatedDigit0 = _random.nextInt(10); _animatedDigit1 = _random.nextInt(10); _animatedDigit2 = _random.nextInt(10); }); final target0 = _random.nextInt(10); final target1 = _random.nextInt(10); final target2 = _random.nextInt(10); int ticks = 0; const interval = Duration(milliseconds: 60); _rollTimer?.cancel(); _rollTimer = Timer.periodic(interval, (timer) { ticks++; // Slot 0 stops after 10 ticks (~600ms) if (ticks < 10) { setState(() => _animatedDigit0 = _random.nextInt(10)); } else if (ticks == 10) { setState(() => _animatedDigit0 = target0); HapticFeedback.mediumImpact(); } // Slot 1 stops after 18 ticks (~1080ms) if (ticks < 18) { setState(() => _animatedDigit1 = _random.nextInt(10)); } else if (ticks == 18) { setState(() => _animatedDigit1 = target1); HapticFeedback.mediumImpact(); } // Slot 2 stops after 26 ticks (~1560ms) if (ticks < 26) { setState(() => _animatedDigit2 = _random.nextInt(10)); } else if (ticks == 26) { setState(() => _animatedDigit2 = target2); HapticFeedback.heavyImpact(); } if (ticks >= 28) { timer.cancel(); setState(() => _internalRolling = false); final finalNumber = target0 * 100 + target1 * 10 + target2; widget.onRollCompleted(finalNumber); } }); } @override void dispose() { _rollTimer?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { final theme = Theme.of(context); final isDark = theme.brightness == Brightness.dark; // Determine current display values for the 3 slots String d0 = '•'; String d1 = '•'; String d2 = '•'; if (_internalRolling) { d0 = _animatedDigit0 != null ? '$_animatedDigit0' : '•'; d1 = _animatedDigit1 != null ? '$_animatedDigit1' : '•'; d2 = _animatedDigit2 != null ? '$_animatedDigit2' : '•'; } else { final text = widget.digits; if (text.isNotEmpty) d0 = text[0]; if (text.length > 1) d1 = text[1]; if (text.length > 2) d2 = text[2]; } return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), decoration: BoxDecoration( color: isDark ? const Color(0xFF181B2E) : theme.colorScheme.surfaceContainerHigh, borderRadius: BorderRadius.circular(22), border: Border.all( color: const Color(0xFFFFD54F).withValues(alpha: 0.3), width: 1.2, ), ), child: Column( children: [ Row( children: [ const GradientStar(size: 15), const SizedBox(width: 6), const Expanded( child: Text( 'КЛЕРОМАНТИЯ • 3× d10', style: TextStyle( fontSize: 11.5, fontWeight: FontWeight.w900, letterSpacing: 0.5, color: Color(0xFFFFD54F), ), ), ), Text( _internalRolling ? 'Бросок...' : (widget.digits.length == 3 ? 'Зафиксировано' : '3 цифры'), style: TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: _internalRolling ? const Color(0xFFBA68C8) : theme.colorScheme.outline, ), ), ], ), const SizedBox(height: 12), // 3 Dice Slots Display Row( mainAxisAlignment: MainAxisAlignment.center, children: [ _buildDieSlot( title: '1-й d10 (Сотни)', value: d0, isActive: widget.digits.isNotEmpty || _internalRolling, isDark: isDark, ), const SizedBox(width: 12), _buildDieSlot( title: '2-й d10 (Десятки)', value: d1, isActive: widget.digits.length >= 2 || _internalRolling, isDark: isDark, ), const SizedBox(width: 12), _buildDieSlot( title: '3-й d10 (Единицы)', value: d2, isActive: widget.digits.length >= 3 || _internalRolling, isDark: isDark, ), ], ), const SizedBox(height: 12), // Action Button: Roll 3xd10 SizedBox( width: double.infinity, child: FilledButton.icon( onPressed: _internalRolling ? null : () { triggerRollAnimation(); widget.onRollPressed(); }, icon: _internalRolling ? const SizedBox( width: 18, height: 18, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Color(0xFFFFD54F)), ), ) : const Icon(Icons.casino_rounded, size: 20, color: Color(0xFFFFD54F)), label: Text( _internalRolling ? 'ВРАЩЕНИЕ ЖРЕБИЯ...' : 'СОВЕРШИТЬ 3 БРОСКА d10', style: const TextStyle( fontWeight: FontWeight.w900, letterSpacing: 0.6, color: Color(0xFFFFF9C4), ), ), style: FilledButton.styleFrom( backgroundColor: const Color(0xFF2A2D4A), padding: const EdgeInsets.symmetric(vertical: 13), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), side: const BorderSide( color: Color(0xFFFFD54F), width: 1.2, ), ), ), ), ), ], ), ); } Widget _buildDieSlot({ required String title, required String value, required bool isActive, required bool isDark, }) { final isFilled = value != '•'; return Expanded( child: Column( children: [ AnimatedContainer( duration: const Duration(milliseconds: 200), height: 64, alignment: Alignment.center, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: isFilled ? [ const Color(0xFF2E2452), const Color(0xFF1E1B38), ] : [ isDark ? const Color(0xFF131525) : const Color(0xFFEAEBF5), isDark ? const Color(0xFF0F111E) : const Color(0xFFDEDFEF), ], ), borderRadius: BorderRadius.circular(16), border: Border.all( color: isFilled ? const Color(0xFFFFD54F) : const Color(0xFFFFD54F).withValues(alpha: 0.25), width: isFilled ? 1.6 : 1.0, ), boxShadow: isFilled ? [ BoxShadow( color: const Color(0xFFFFD54F).withValues(alpha: 0.2), blurRadius: 10, spreadRadius: 1, ), ] : null, ), child: Text( value, style: TextStyle( fontSize: 28, fontWeight: FontWeight.w900, color: isFilled ? const Color(0xFFFFF9C4) : isDark ? Colors.white24 : Colors.black26, ), ), ), const SizedBox(height: 6), Text( title, textAlign: TextAlign.center, style: const TextStyle( fontSize: 10.5, fontWeight: FontWeight.w600, color: Color(0xFFFFD54F), ), ), ], ), ); } }