773 lines
27 KiB
Dart
773 lines
27 KiB
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/words_repository.dart';
|
|
import '../widgets/d10_dice_roller_widget.dart';
|
|
import '../widgets/gradient_star_badge.dart';
|
|
|
|
class SessionResultScreen extends StatefulWidget {
|
|
final OracleSession session;
|
|
|
|
const SessionResultScreen({
|
|
super.key,
|
|
required this.session,
|
|
});
|
|
|
|
@override
|
|
State<SessionResultScreen> createState() => _SessionResultScreenState();
|
|
}
|
|
|
|
class _SessionResultScreenState extends State<SessionResultScreen> {
|
|
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);
|
|
|
|
// If initial interpretation is not yet done, trigger it
|
|
if (_session.aiInterpretation == null) {
|
|
_fetchInitialAi();
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_chatInputController.dispose();
|
|
_scrollController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _fetchInitialAi() async {
|
|
setState(() => _isGeneratingInitialAi = true);
|
|
|
|
try {
|
|
final result = await _aiService.interpretSession(
|
|
words: _session.words,
|
|
question: _session.question ?? '',
|
|
);
|
|
|
|
if (mounted) {
|
|
final initialMessage = SessionChatMessage(
|
|
id: 'msg_0',
|
|
sender: 'oracle',
|
|
text: result,
|
|
timestamp: DateTime.now(),
|
|
);
|
|
|
|
final updated = _session.copyWith(
|
|
aiInterpretation: result,
|
|
messages: _session.messages.isEmpty ? [initialMessage] : _session.messages,
|
|
);
|
|
|
|
setState(() {
|
|
_session = updated;
|
|
_isGeneratingInitialAi = false;
|
|
});
|
|
await _repo.saveSession(updated);
|
|
_scrollToBottom();
|
|
}
|
|
} catch (_) {
|
|
if (mounted) {
|
|
setState(() => _isGeneratingInitialAi = false);
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _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 _copySessionToClipboard() {
|
|
final buf = StringBuffer();
|
|
buf.writeln('✨ Оракул 999 — Сессия Клеромантии ✨');
|
|
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.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),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Live Chat Area
|
|
Expanded(
|
|
child: _session.messages.isEmpty && _isGeneratingInitialAi
|
|
? const Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
CircularProgressIndicator(
|
|
valueColor: AlwaysStoppedAnimation<Color>(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(
|
|
mainAxisSize: MainAxisSize.min,
|
|
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 ? 'Вы' : 'Оракул 999',
|
|
style: TextStyle(
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.w900,
|
|
color: isUser ? const Color(0xFFFFD54F) : const Color(0xFFE1BEE7),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
// Attached new word pill if present
|
|
if (msg.attachedWord != null) ...[
|
|
const SizedBox(height: 6),
|
|
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.4)),
|
|
),
|
|
child: Text(
|
|
'🎲 Добавлен знак: #${msg.attachedWord!.formattedSessionNumber} «${msg.attachedWord!.word.toUpperCase()}»',
|
|
style: const TextStyle(
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.bold,
|
|
color: Color(0xFFFFF9C4),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
|
|
const SizedBox(height: 6),
|
|
|
|
// Message text
|
|
Text(
|
|
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>(Color(0xFFE1BEE7)),
|
|
),
|
|
),
|
|
SizedBox(width: 8),
|
|
Text(
|
|
'Оракул внимает и толкует знаки...',
|
|
style: TextStyle(fontSize: 12, color: Color(0xFFE1BEE7), fontStyle: FontStyle.italic),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showWordDetails(SessionWord word) {
|
|
showDialog(
|
|
context: context,
|
|
builder: (ctx) => AlertDialog(
|
|
title: Row(
|
|
children: [
|
|
const GradientStar(size: 20),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'#${word.formattedSessionNumber} ${word.word.toUpperCase()}',
|
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
],
|
|
),
|
|
content: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'${word.partOfSpeech} • ${word.category}',
|
|
style: TextStyle(color: Theme.of(ctx).colorScheme.outline),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
word.symbolicMeaning,
|
|
style: const TextStyle(fontSize: 14, height: 1.45),
|
|
),
|
|
],
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(ctx),
|
|
child: const Text('Закрыть'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Modal Bottom Sheet for rolling or typing an additional d10 word
|
|
class _AddWordBottomSheet extends StatefulWidget {
|
|
final ValueChanged<WordItem> 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<String> 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(),
|
|
);
|
|
}
|
|
}
|