import '../models/syncretic_session_model.dart'; import '../services/oracle_master_prompt_generator.dart'; import 'symbol_detail_screen.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../models/history_entry_model.dart'; import '../models/word_model.dart'; import '../services/ai_oracle_service.dart'; import '../services/settings_repository.dart'; import '../services/words_repository.dart'; import '../widgets/d10_dice_roller_widget.dart'; import '../widgets/gradient_star_badge.dart'; import '../widgets/interpreter_persona_picker.dart'; class SessionResultScreen extends StatefulWidget { final OracleSession session; const SessionResultScreen({ super.key, required this.session, }); @override State createState() => _SessionResultScreenState(); } class _SessionResultScreenState extends State { final _aiService = AiOracleService(); final _repo = WordsRepository(); final _chatInputController = TextEditingController(); final _scrollController = ScrollController(); late OracleSession _session; bool _isGeneratingInitialAi = false; bool _isChatLoading = false; @override void initState() { super.initState(); _session = widget.session; _repo.saveSession(_session); // Auto-record this session into active Syncretic Day Session final readingItems = _session.words.map((w) { final fw = _repo.getWordByName(w.word) ?? _repo.getWordBySessionNumber(w.sessionNumber); return ReadingSignItem( title: '#${w.formattedSessionNumber} ${w.word.toUpperCase()}', subtitle: w.category, symbol: '🎲', lore: w.symbolicMeaning, linkedWord: fw, ); }).toList(); _repo.addReadingToSyncreticSession( SyncreticReading( id: 'reading_${_session.id}', systemId: 'cleromancy', systemName: 'Оракул (Клеромантия)', systemIcon: '🎲', timestamp: _session.timestamp, layoutTitle: '${_session.words.length} ${_session.words.length == 1 ? "знак" : "знаков"}', inputMethod: 'dice', items: readingItems, question: _session.question, ), question: _session.question, ); // If initial interpretation is not yet done, trigger it if (_session.aiInterpretation == null) { _fetchInitialAi(); } } @override void dispose() { _chatInputController.dispose(); _scrollController.dispose(); super.dispose(); } Future _fetchInitialAi({bool replaceOpening = true}) async { setState(() => _isGeneratingInitialAi = true); try { final voice = SettingsRepository().persona; final result = await _aiService.interpretSession( words: _session.words, question: _session.question ?? '', ); if (mounted) { final labeled = '[${voice.title}] $result'; final message = SessionChatMessage( id: 'msg_${DateTime.now().millisecondsSinceEpoch}', sender: 'oracle', text: labeled, timestamp: DateTime.now(), ); final nextMessages = replaceOpening && _session.messages.isEmpty ? [message] : [..._session.messages, message]; final updated = _session.copyWith( aiInterpretation: labeled, messages: nextMessages, ); setState(() { _session = updated; _isGeneratingInitialAi = false; }); await _repo.saveSession(updated); _scrollToBottom(); } } catch (_) { if (mounted) { setState(() => _isGeneratingInitialAi = false); } } } Future _sendMessage({SessionWord? newlyAddedWord}) async { final text = _chatInputController.text.trim(); if (text.isEmpty && newlyAddedWord == null) return; final userMessageText = text.isNotEmpty ? text : 'Раскрой значение добавленного знака #${newlyAddedWord?.formattedSessionNumber} «${newlyAddedWord?.word.toUpperCase()}».'; _chatInputController.clear(); final userMsg = SessionChatMessage( id: 'msg_${DateTime.now().millisecondsSinceEpoch}', sender: 'user', text: userMessageText, timestamp: DateTime.now(), attachedWord: newlyAddedWord, ); final updatedWithUser = _session.copyWith( messages: [..._session.messages, userMsg], ); setState(() { _session = updatedWithUser; _isChatLoading = true; }); _scrollToBottom(); try { final reply = await _aiService.chatWithOracle( session: updatedWithUser, userMessage: userMessageText, newlyAddedWord: newlyAddedWord, ); if (mounted) { final oracleMsg = SessionChatMessage( id: 'msg_${DateTime.now().millisecondsSinceEpoch + 1}', sender: 'oracle', text: reply, timestamp: DateTime.now(), ); final finalSession = updatedWithUser.copyWith( messages: [...updatedWithUser.messages, oracleMsg], ); setState(() { _session = finalSession; _isChatLoading = false; }); await _repo.saveSession(finalSession); _scrollToBottom(); } } catch (e) { if (mounted) { setState(() => _isChatLoading = false); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Ошибка отправки: $e')), ); } } } void _scrollToBottom() { WidgetsBinding.instance.addPostFrameCallback((_) { if (_scrollController.hasClients) { _scrollController.animateTo( _scrollController.position.maxScrollExtent + 100, duration: const Duration(milliseconds: 300), curve: Curves.easeOut, ); } }); } void _showAddWordModal() { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (ctx) => _AddWordBottomSheet( onWordChosen: (WordItem word) async { Navigator.pop(ctx); final sessionWord = SessionWord.fromWordItem( word, positionTitle: 'Уточняющий знак #${_session.words.length + 1}', ); final updated = _session.copyWith( words: [..._session.words, sessionWord], wordsCount: _session.words.length + 1, ); setState(() { _session = updated; }); await _repo.saveSession(updated); // Trigger chat refinement with this new word await _sendMessage(newlyAddedWord: sessionWord); }, ), ); } void _showMasterPromptDialog() { HapticFeedback.mediumImpact(); final prompt = OracleMasterPromptGenerator.generateForSession(_session); showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: const Color(0xFF0F1222), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), builder: (ctx) { return DraggableScrollableSheet( initialChildSize: 0.85, minChildSize: 0.5, maxChildSize: 0.95, expand: false, builder: (context, scrollController) { return Container( padding: const EdgeInsets.fromLTRB(20, 12, 20, 20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Center( child: Container( width: 44, height: 4, decoration: BoxDecoration( color: Colors.white24, borderRadius: BorderRadius.circular(2), ), ), ), const SizedBox(height: 14), Row( children: [ const Icon(Icons.psychology_alt_rounded, color: Color(0xFFFFD54F), size: 24), const SizedBox(width: 10), const Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Мастер-Промпт Сессии', style: TextStyle(color: Colors.white, fontSize: 17, fontWeight: FontWeight.bold), ), Text( 'Для отправки в Grok, Claude 3.5 Sonnet, Gemini 1.5 Pro, GPT-4o', style: TextStyle(color: Color(0xFF90A4AE), fontSize: 11), ), ], ), ), IconButton( icon: const Icon(Icons.close_rounded, color: Color(0xFF90A4AE)), onPressed: () => Navigator.pop(ctx), ), ], ), const SizedBox(height: 12), // Quick Actions Bar Row( children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: const Color(0xFF1C223D), borderRadius: BorderRadius.circular(8), ), child: Text( '${prompt.length} символов • ${prompt.split(RegExp(r"\s+")).length} слов', style: const TextStyle(color: Color(0xFF90CAF9), fontSize: 11), ), ), const Spacer(), ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFFFD54F), foregroundColor: Colors.black, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), ), onPressed: () { Clipboard.setData(ClipboardData(text: prompt)); HapticFeedback.heavyImpact(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Мастер-промпт сессии скопирован! Вставьте в Grok или Claude.'), backgroundColor: Color(0xFF388E3C), behavior: SnackBarBehavior.floating, ), ); }, icon: const Icon(Icons.copy_rounded, size: 16), label: const Text('Скопировать всё', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12.5)), ), ], ), const SizedBox(height: 12), // Scrollable Prompt Text Area Expanded( child: Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: const Color(0xFF090A14), borderRadius: BorderRadius.circular(16), border: Border.all(color: const Color(0xFF232742), width: 1.0), ), child: SingleChildScrollView( controller: scrollController, child: SelectableText( prompt, style: const TextStyle( color: Color(0xFFECEFF1), fontFamily: 'monospace', fontSize: 11.5, height: 1.45, ), ), ), ), ), ], ), ); }, ); }, ); } void _copySessionToClipboard() { final buf = StringBuffer(); buf.writeln('✨ Оракул — Сессия Клеромантии ✨'); buf.writeln('Дата: ${_session.formattedDate}'); if (_session.question != null && _session.question!.isNotEmpty) { buf.writeln('Вопрос: «${_session.question}»'); } buf.writeln('Знаков в цепочке: ${_session.words.length}\n'); for (int i = 0; i < _session.words.length; i++) { final w = _session.words[i]; final pos = w.positionTitle != null ? ' (${w.positionTitle})' : ''; buf.writeln('🔮 Знак #${w.formattedSessionNumber}: ${w.word.toUpperCase()}$pos'); buf.writeln(' ${w.symbolicMeaning}\n'); } if (_session.aiInterpretation != null) { buf.writeln('🤖 Толкование Оракула (G4F):'); buf.writeln(_session.aiInterpretation); } Clipboard.setData(ClipboardData(text: buf.toString())); HapticFeedback.lightImpact(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Row( children: [ GradientStar(size: 18), SizedBox(width: 10), Expanded(child: Text('Сессия скопирована в буфер обмена')), ], ), duration: const Duration(seconds: 2), behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), ); } @override Widget build(BuildContext context) { final theme = Theme.of(context); final isDark = theme.brightness == Brightness.dark; return Scaffold( appBar: AppBar( title: Row( children: [ const GradientStar(size: 22), const SizedBox(width: 8), Text( 'Сессия (${_session.words.length} ${_session.words.length == 1 ? "знак" : (_session.words.length < 5 ? "знака" : "знаков")})', style: const TextStyle(fontWeight: FontWeight.w900), ), ], ), actions: [ IconButton( tooltip: 'Мастер-промпт для нейросетей', icon: const Icon(Icons.psychology_alt_rounded, color: Color(0xFFFFD54F)), onPressed: _showMasterPromptDialog, ), IconButton( tooltip: 'Докинуть уточняющий знак', icon: const Icon(Icons.add_circle_outline_rounded, color: Color(0xFFFFD54F)), onPressed: _showAddWordModal, ), IconButton( tooltip: 'Скопировать сессию', icon: const Icon(Icons.copy_rounded), onPressed: _copySessionToClipboard, ), ], ), body: SafeArea( child: Column( children: [ // Top Collapsible / Horizontal Words Strip Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), decoration: BoxDecoration( color: isDark ? const Color(0xFF141728) : theme.colorScheme.surfaceContainerLow, border: const Border( bottom: BorderSide(color: Colors.white12, width: 1), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Question line if present if (_session.question != null && _session.question!.isNotEmpty) ...[ Padding( padding: const EdgeInsets.only(bottom: 6), child: Row( children: [ const Icon(Icons.help_outline_rounded, size: 14, color: Color(0xFFFFD54F)), const SizedBox(width: 6), Expanded( child: Text( 'Вопрос: «${_session.question}»', maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFFFFF9C4), fontStyle: FontStyle.italic, ), ), ), ], ), ), ], // Words Horizontal Badges List + Add Button SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ ..._session.words.asMap().entries.map((e) { final idx = e.key; final w = e.value; return Padding( padding: const EdgeInsets.only(right: 6), child: InkWell( onTap: () => _showWordDetails(w), borderRadius: BorderRadius.circular(12), child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), decoration: BoxDecoration( color: const Color(0xFF232742), borderRadius: BorderRadius.circular(12), border: Border.all( color: const Color(0xFFFFD54F).withValues(alpha: 0.4), ), ), child: Row( children: [ Text( '${idx + 1}. ', style: TextStyle( fontSize: 11, color: theme.colorScheme.outline, fontWeight: FontWeight.bold, ), ), Text( '#${w.formattedSessionNumber} ${w.word.toUpperCase()}', style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w900, color: Color(0xFFFFD54F), ), ), ], ), ), ), ); }), // Button: Add word OutlinedButton.icon( onPressed: _showAddWordModal, icon: const Icon(Icons.casino_rounded, size: 14, color: Color(0xFFFFD54F)), label: const Text( '+ Докинуть знак', style: TextStyle( fontSize: 11.5, fontWeight: FontWeight.bold, color: Color(0xFFFFD54F), ), ), style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), side: const BorderSide(color: Color(0xFFFFD54F)), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), ), ], ), ), const SizedBox(height: 10), Row( children: [ Expanded( child: InterpreterPersonaPicker( compact: true, onSelected: (_) { if (_isGeneratingInitialAi || _isChatLoading) return; _fetchInitialAi(replaceOpening: false); }, ), ), const SizedBox(width: 8), InkWell( onTap: _showMasterPromptDialog, borderRadius: BorderRadius.circular(10), child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( color: const Color(0xFF2A1F45), borderRadius: BorderRadius.circular(10), border: Border.all(color: const Color(0xFFBA68C8).withValues(alpha: 0.6)), ), child: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.auto_awesome_rounded, size: 14, color: Color(0xFFFFD54F)), SizedBox(width: 5), Text( 'Мастер-Промпт', style: TextStyle( color: Color(0xFFFFF9C4), fontSize: 11.5, fontWeight: FontWeight.bold, ), ), ], ), ), ), ], ), ], ), ), // Live Chat Area Expanded( child: _session.messages.isEmpty && _isGeneratingInitialAi ? const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator( valueColor: AlwaysStoppedAnimation(Color(0xFFFFD54F)), ), SizedBox(height: 16), Text( 'Оракул синтезирует символы сессии...', style: TextStyle(fontStyle: FontStyle.italic), ), ], ), ) : ListView.builder( controller: _scrollController, padding: const EdgeInsets.fromLTRB(14, 12, 14, 12), itemCount: _session.messages.length + (_isChatLoading ? 1 : 0), itemBuilder: (context, idx) { if (idx == _session.messages.length) { return _buildTypingIndicator(); } final msg = _session.messages[idx]; final isUser = msg.sender == 'user'; return _buildChatBubble(msg, isUser, isDark, theme); }, ), ), // Bottom Chat Input Bar Container( padding: const EdgeInsets.fromLTRB(10, 8, 10, 10), decoration: BoxDecoration( color: isDark ? const Color(0xFF141728) : theme.colorScheme.surfaceContainerHigh, border: const Border( top: BorderSide(color: Colors.white12, width: 1), ), ), child: Row( children: [ IconButton( tooltip: 'Докинуть уточняющий знак d10', icon: const Icon(Icons.casino_rounded, color: Color(0xFFFFD54F), size: 24), onPressed: _showAddWordModal, ), const SizedBox(width: 4), Expanded( child: TextField( controller: _chatInputController, textInputAction: TextInputAction.send, onSubmitted: (_) => _sendMessage(), decoration: InputDecoration( hintText: 'Задайте уточняющий вопрос оракулу...', hintStyle: const TextStyle(fontSize: 13), filled: true, fillColor: isDark ? const Color(0xFF1F223B) : theme.colorScheme.surface, contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), border: OutlineInputBorder( borderRadius: BorderRadius.circular(20), borderSide: BorderSide.none, ), ), ), ), const SizedBox(width: 6), IconButton.filled( onPressed: _isChatLoading ? null : () => _sendMessage(), icon: const Icon(Icons.arrow_upward_rounded, size: 20), style: IconButton.styleFrom( backgroundColor: const Color(0xFF2A2D4A), foregroundColor: const Color(0xFFFFD54F), side: const BorderSide(color: Color(0xFFFFD54F), width: 1.2), ), ), ], ), ), ], ), ), ); } Widget _buildChatBubble( SessionChatMessage msg, bool isUser, bool isDark, ThemeData theme) { return Align( alignment: isUser ? Alignment.centerRight : Alignment.centerLeft, child: Container( margin: const EdgeInsets.only(bottom: 12), constraints: BoxConstraints( maxWidth: MediaQuery.of(context).size.width * 0.85, ), padding: const EdgeInsets.all(14), decoration: BoxDecoration( gradient: isUser ? const LinearGradient( colors: [Color(0xFF2A2D4A), Color(0xFF1E2138)], ) : const LinearGradient( colors: [Color(0xFF2B1F4A), Color(0xFF1A1B35)], ), borderRadius: BorderRadius.only( topLeft: const Radius.circular(18), topRight: const Radius.circular(18), bottomLeft: Radius.circular(isUser ? 18 : 4), bottomRight: Radius.circular(isUser ? 4 : 18), ), border: Border.all( color: isUser ? const Color(0xFFFFD54F).withValues(alpha: 0.3) : const Color(0xFFBA68C8).withValues(alpha: 0.45), width: 1.2, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header: Sender icon & name Row( children: [ Icon( isUser ? Icons.person_outline_rounded : Icons.psychology_rounded, size: 14, color: isUser ? const Color(0xFFFFD54F) : const Color(0xFFE1BEE7), ), const SizedBox(width: 5), Text( isUser ? 'Вы' : 'Оракул', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w900, color: isUser ? const Color(0xFFFFD54F) : const Color(0xFFE1BEE7), ), ), const Spacer(), IconButton( tooltip: 'Копировать текст', visualDensity: VisualDensity.compact, padding: EdgeInsets.zero, constraints: const BoxConstraints(minWidth: 28, minHeight: 28), icon: Icon( Icons.copy_rounded, size: 15, color: isUser ? const Color(0xFFFFD54F) : const Color(0xFFE1BEE7), ), onPressed: () { Clipboard.setData(ClipboardData(text: msg.text)); HapticFeedback.lightImpact(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Текст скопирован'), duration: Duration(seconds: 1), behavior: SnackBarBehavior.floating, ), ); }, ), ], ), // Attached new word pill if present if (msg.attachedWord != null) ...[ const SizedBox(height: 6), InkWell( onTap: () => _showWordDetails(msg.attachedWord!), borderRadius: BorderRadius.circular(8), child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( color: const Color(0xFF3B2B5E), borderRadius: BorderRadius.circular(8), border: Border.all(color: const Color(0xFFFFD54F).withValues(alpha: 0.5)), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Text( '🎲 Добавлен знак: #${msg.attachedWord!.formattedSessionNumber} «${msg.attachedWord!.word.toUpperCase()}»', style: const TextStyle( fontSize: 11, fontWeight: FontWeight.bold, color: Color(0xFFFFF9C4), ), ), const SizedBox(width: 4), const Icon(Icons.arrow_forward_ios_rounded, size: 10, color: Color(0xFFFFD54F)), ], ), ), ), ], const SizedBox(height: 6), SelectableText( msg.text, style: TextStyle( fontSize: 13.5, height: 1.45, color: isUser ? Colors.white : const Color(0xFFF3E5F5), ), ), ], ), ), ); } Widget _buildTypingIndicator() { return Align( alignment: Alignment.centerLeft, child: Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), decoration: BoxDecoration( color: const Color(0xFF241B3B), borderRadius: BorderRadius.circular(16), border: Border.all(color: const Color(0xFFBA68C8).withValues(alpha: 0.3)), ), child: const Row( mainAxisSize: MainAxisSize.min, children: [ SizedBox( width: 14, height: 14, child: CircularProgressIndicator( strokeWidth: 1.8, valueColor: AlwaysStoppedAnimation(Color(0xFFE1BEE7)), ), ), SizedBox(width: 8), Text( 'Оракул внимает и толкует знаки...', style: TextStyle(fontSize: 12, color: Color(0xFFE1BEE7), fontStyle: FontStyle.italic), ), ], ), ), ); } void _showWordDetails(SessionWord word) { final fullWord = _repo.getWordByName(word.word) ?? _repo.getWordBySessionNumber(word.sessionNumber) ?? WordItem( id: word.sessionNumber, word: word.word, partOfSpeech: word.partOfSpeech.isNotEmpty ? word.partOfSpeech : 'знак', frequency: 0, category: word.category, description: word.symbolicMeaning, ); Navigator.push( context, MaterialPageRoute( builder: (context) => SymbolDetailScreen(word: fullWord), ), ); } } /// Modal Bottom Sheet for rolling or typing an additional d10 word class _AddWordBottomSheet extends StatefulWidget { final ValueChanged onWordChosen; const _AddWordBottomSheet({required this.onWordChosen}); @override State<_AddWordBottomSheet> createState() => _AddWordBottomSheetState(); } class _AddWordBottomSheetState extends State<_AddWordBottomSheet> { final _repo = WordsRepository(); String _digits = ''; void _onRollCompleted(int finalNumber) { final word = _repo.getWordBySessionNumber(finalNumber); if (word != null) { widget.onWordChosen(word); } } void _onKeypadTap(String val) { HapticFeedback.lightImpact(); if (val == 'C') { setState(() => _digits = ''); return; } if (val == '⌫') { if (_digits.isNotEmpty) { setState(() => _digits = _digits.substring(0, _digits.length - 1)); } return; } String newDigits = _digits.length >= 3 ? val : _digits + val; setState(() => _digits = newDigits); if (newDigits.length == 3) { final num = int.tryParse(newDigits); if (num != null && num <= 999) { final word = _repo.getWordBySessionNumber(num); if (word != null) { widget.onWordChosen(word); } } } } @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; return Container( padding: const EdgeInsets.fromLTRB(16, 12, 16, 24), decoration: BoxDecoration( color: isDark ? const Color(0xFF141728) : Theme.of(context).colorScheme.surface, borderRadius: const BorderRadius.vertical(top: Radius.circular(28)), border: Border.all(color: const Color(0xFFFFD54F).withValues(alpha: 0.35)), ), child: SafeArea( child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 40, height: 4, decoration: BoxDecoration( color: const Color(0xFFFFD54F).withValues(alpha: 0.5), borderRadius: BorderRadius.circular(2), ), ), const SizedBox(height: 12), const Row( children: [ GradientStar(size: 20), SizedBox(width: 8), Text( 'Докинуть уточняющий знак (3× d10)', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w900), ), ], ), const SizedBox(height: 12), D10DiceRollerWidget( digits: _digits, isRolling: false, onRollPressed: () {}, onRollCompleted: _onRollCompleted, ), const SizedBox(height: 12), // Keypad for 3 digits _buildKeypadRow(['1', '2', '3']), const SizedBox(height: 6), _buildKeypadRow(['4', '5', '6']), const SizedBox(height: 6), _buildKeypadRow(['7', '8', '9']), const SizedBox(height: 6), _buildKeypadRow(['C', '0', '⌫']), ], ), ), ), ); } Widget _buildKeypadRow(List keys) { final isDark = Theme.of(context).brightness == Brightness.dark; return Row( children: keys.map((key) { final isSpecial = key == 'C' || key == '⌫'; return Expanded( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 4), child: Material( color: isSpecial ? const Color(0xFF2A2D4A) : (isDark ? const Color(0xFF20233A) : Colors.black12), borderRadius: BorderRadius.circular(12), child: InkWell( onTap: () => _onKeypadTap(key), borderRadius: BorderRadius.circular(12), child: Container( height: 42, alignment: Alignment.center, child: Text( key, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: isSpecial ? const Color(0xFFFFF9C4) : Colors.white, ), ), ), ), ), ), ); }).toList(), ); } }