478 lines
17 KiB
Dart
478 lines
17 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/words_repository.dart';
|
||
import '../widgets/cleromancy_dialog.dart';
|
||
import '../widgets/d10_dice_roller_widget.dart';
|
||
import '../widgets/gradient_star_badge.dart';
|
||
import '../widgets/word_card_widget.dart';
|
||
|
||
class HomeSearchScreen extends StatefulWidget {
|
||
const HomeSearchScreen({super.key});
|
||
|
||
@override
|
||
State<HomeSearchScreen> createState() => _HomeSearchScreenState();
|
||
}
|
||
|
||
class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
||
final _repo = WordsRepository();
|
||
final _questionController = TextEditingController();
|
||
final _diceRollerKey = GlobalKey<D10DiceRollerWidgetState>();
|
||
|
||
WordItem? _currentWord;
|
||
String? _activeHistoryId;
|
||
bool _showKeypad = true;
|
||
String _currentDigits = ''; // Tracks 0..3 digits input: e.g. "", "7", "74", "742"
|
||
|
||
@override
|
||
void dispose() {
|
||
_questionController.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
Future<void> _setWord(WordItem word, {bool recordHistory = true}) async {
|
||
final question = _questionController.text.trim();
|
||
String? newHistId;
|
||
|
||
if (recordHistory) {
|
||
newHistId = DateTime.now().millisecondsSinceEpoch.toString();
|
||
final entry = HistoryEntry(
|
||
id: newHistId,
|
||
timestamp: DateTime.now(),
|
||
word: word.word,
|
||
sessionNumber: word.sessionNumber,
|
||
partOfSpeech: word.partOfSpeech,
|
||
symbolicMeaning: word.description,
|
||
readingType: 'Клеромантия (3× d10)',
|
||
question: question.isNotEmpty ? question : null,
|
||
);
|
||
await _repo.addHistoryEntry(entry);
|
||
}
|
||
|
||
setState(() {
|
||
_currentWord = word;
|
||
_activeHistoryId = newHistId ?? _activeHistoryId;
|
||
_currentDigits = word.formattedSessionNumber;
|
||
});
|
||
HapticFeedback.selectionClick();
|
||
}
|
||
|
||
void _onKeypadTap(String val) {
|
||
HapticFeedback.lightImpact();
|
||
|
||
if (val == 'C') {
|
||
setState(() {
|
||
_currentDigits = '';
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (val == '⌫') {
|
||
if (_currentDigits.isNotEmpty) {
|
||
setState(() {
|
||
_currentDigits = _currentDigits.substring(0, _currentDigits.length - 1);
|
||
});
|
||
}
|
||
return;
|
||
}
|
||
|
||
// Append digit up to 3 digits (1st d10, 2nd d10, 3rd d10)
|
||
String newDigits;
|
||
if (_currentDigits.length >= 3) {
|
||
newDigits = val;
|
||
} else {
|
||
newDigits = _currentDigits + val;
|
||
}
|
||
|
||
setState(() {
|
||
_currentDigits = newDigits;
|
||
});
|
||
|
||
// Only resolve word when all 3 digits are provided!
|
||
if (newDigits.length == 3) {
|
||
final num = int.tryParse(newDigits);
|
||
if (num != null && num <= 999) {
|
||
final word = _repo.getWordBySessionNumber(num);
|
||
if (word != null) {
|
||
_setWord(word, recordHistory: true);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void _onVirtualRollCompleted(int finalNumber) {
|
||
final word = _repo.getWordBySessionNumber(finalNumber);
|
||
if (word != null) {
|
||
_setWord(word, recordHistory: true);
|
||
}
|
||
}
|
||
|
||
void _resetToWelcome() {
|
||
setState(() {
|
||
_currentWord = null;
|
||
_currentDigits = '';
|
||
_activeHistoryId = null;
|
||
});
|
||
}
|
||
|
||
void _goToNext() {
|
||
if (_currentWord == null) return;
|
||
final next = _repo.getNextWord(_currentWord!.sessionNumber);
|
||
_setWord(next, recordHistory: false);
|
||
}
|
||
|
||
void _goToPrevious() {
|
||
if (_currentWord == null) return;
|
||
final prev = _repo.getPreviousWord(_currentWord!.sessionNumber);
|
||
_setWord(prev, recordHistory: false);
|
||
}
|
||
|
||
void _reshuffleSession() {
|
||
_repo.reshuffleSessionNumbers();
|
||
_resetToWelcome();
|
||
HapticFeedback.heavyImpact();
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: const Row(
|
||
children: [
|
||
GradientStar(size: 20),
|
||
SizedBox(width: 10),
|
||
Expanded(
|
||
child: Text(
|
||
'Случайный порядок 1000 чисел перетасован заново!',
|
||
style: TextStyle(fontWeight: FontWeight.w600),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
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: const Row(
|
||
children: [
|
||
GradientStar(size: 24),
|
||
SizedBox(width: 10),
|
||
Text(
|
||
'Оракул 999',
|
||
style: TextStyle(
|
||
fontSize: 20,
|
||
fontWeight: FontWeight.w900,
|
||
letterSpacing: 0.5,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
actions: [
|
||
IconButton(
|
||
tooltip: 'Инструкция: Клеромантия и кубики d10',
|
||
icon: const Icon(Icons.help_outline_rounded, color: Color(0xFFFFD54F)),
|
||
onPressed: () => CleromancyGuideDialog.show(context),
|
||
),
|
||
if (_currentWord != null)
|
||
IconButton(
|
||
tooltip: 'Новый вопрос / Сброс',
|
||
icon: const Icon(Icons.refresh_rounded),
|
||
onPressed: _resetToWelcome,
|
||
),
|
||
IconButton(
|
||
tooltip: 'Перетасовать матрицу (#000..#999)',
|
||
icon: const Icon(Icons.shuffle_rounded),
|
||
onPressed: _reshuffleSession,
|
||
),
|
||
IconButton(
|
||
tooltip: _showKeypad ? 'Скрыть панель' : 'Показать панель',
|
||
icon: Icon(_showKeypad ? Icons.dialpad_outlined : Icons.dialpad),
|
||
onPressed: () {
|
||
setState(() => _showKeypad = !_showKeypad);
|
||
},
|
||
),
|
||
],
|
||
),
|
||
body: SafeArea(
|
||
child: SingleChildScrollView(
|
||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||
children: [
|
||
// Question Input Field
|
||
TextField(
|
||
controller: _questionController,
|
||
decoration: InputDecoration(
|
||
labelText: 'Ваш вопрос или намерение (необязательно)',
|
||
hintText: 'Например: В чем корень текущей ситуации?',
|
||
prefixIcon: const Icon(Icons.help_outline_rounded, color: Color(0xFFFFD54F), size: 20),
|
||
suffixIcon: _questionController.text.isNotEmpty
|
||
? IconButton(
|
||
icon: const Icon(Icons.clear, size: 18),
|
||
onPressed: () => _questionController.clear(),
|
||
)
|
||
: null,
|
||
filled: true,
|
||
fillColor: isDark ? const Color(0xFF17192C) : theme.colorScheme.surfaceContainerHigh,
|
||
border: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(16),
|
||
borderSide: BorderSide(
|
||
color: const Color(0xFFFFD54F).withValues(alpha: 0.25),
|
||
),
|
||
),
|
||
enabledBorder: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(16),
|
||
borderSide: BorderSide(
|
||
color: const Color(0xFFFFD54F).withValues(alpha: 0.2),
|
||
),
|
||
),
|
||
focusedBorder: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(16),
|
||
borderSide: const BorderSide(
|
||
color: Color(0xFFFFD54F),
|
||
width: 1.5,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
|
||
const SizedBox(height: 12),
|
||
|
||
// D10 Dice Roller Slots Widget (Clean & without prefilled junk)
|
||
D10DiceRollerWidget(
|
||
key: _diceRollerKey,
|
||
digits: _currentDigits,
|
||
isRolling: false,
|
||
onRollPressed: () {},
|
||
onRollCompleted: _onVirtualRollCompleted,
|
||
),
|
||
|
||
const SizedBox(height: 14),
|
||
|
||
// Welcome Idle State OR Active Word Card
|
||
if (_currentWord == null) ...[
|
||
// Welcome Initiation Card
|
||
Container(
|
||
padding: const EdgeInsets.all(20),
|
||
decoration: BoxDecoration(
|
||
gradient: LinearGradient(
|
||
begin: Alignment.topLeft,
|
||
end: Alignment.bottomRight,
|
||
colors: [
|
||
const Color(0xFF1F223B),
|
||
const Color(0xFF131526),
|
||
],
|
||
),
|
||
borderRadius: BorderRadius.circular(24),
|
||
border: Border.all(
|
||
color: const Color(0xFFFFD54F).withValues(alpha: 0.35),
|
||
width: 1.2,
|
||
),
|
||
),
|
||
child: Column(
|
||
children: [
|
||
const GradientStar(size: 38),
|
||
const SizedBox(height: 12),
|
||
const Text(
|
||
'СВЯЩЕННЫЙ ЖРЕБИЙ КЛЕРОМАНТИИ',
|
||
textAlign: TextAlign.center,
|
||
style: TextStyle(
|
||
fontSize: 15,
|
||
fontWeight: FontWeight.w900,
|
||
letterSpacing: 0.8,
|
||
color: Color(0xFFFFF9C4),
|
||
),
|
||
),
|
||
const SizedBox(height: 8),
|
||
Text(
|
||
'Сформулируйте вопрос, затем совершите 3 броска кубика d10 кнопкой выше или наберите 3 выпавших грани на панели.',
|
||
textAlign: TextAlign.center,
|
||
style: TextStyle(
|
||
fontSize: 13,
|
||
height: 1.4,
|
||
color: theme.colorScheme.onSurface.withValues(alpha: 0.85),
|
||
),
|
||
),
|
||
const SizedBox(height: 14),
|
||
OutlinedButton.icon(
|
||
onPressed: () => CleromancyGuideDialog.show(context),
|
||
icon: const Icon(Icons.menu_book_rounded, size: 16, color: Color(0xFFFFD54F)),
|
||
label: const Text(
|
||
'Инструкция по броскам d10',
|
||
style: TextStyle(
|
||
color: Color(0xFFFFD54F),
|
||
fontWeight: FontWeight.bold,
|
||
fontSize: 12.5,
|
||
),
|
||
),
|
||
style: OutlinedButton.styleFrom(
|
||
side: const BorderSide(color: Color(0xFFFFD54F), width: 1),
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(14),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
] else ...[
|
||
// Main Sacred Word Card Display
|
||
AnimatedSwitcher(
|
||
duration: const Duration(milliseconds: 300),
|
||
transitionBuilder: (child, animation) {
|
||
return FadeTransition(
|
||
opacity: animation,
|
||
child: SlideTransition(
|
||
position: Tween<Offset>(
|
||
begin: const Offset(0.0, 0.04),
|
||
end: Offset.zero,
|
||
).animate(animation),
|
||
child: child,
|
||
),
|
||
);
|
||
},
|
||
child: WordCardWidget(
|
||
key: ValueKey('${_currentWord!.id}_${_currentWord!.sessionNumber}'),
|
||
word: _currentWord!,
|
||
question: _questionController.text.trim(),
|
||
historyEntryId: _activeHistoryId,
|
||
onNext: _goToNext,
|
||
onPrevious: _goToPrevious,
|
||
onRandom: () {
|
||
_diceRollerKey.currentState?.triggerRollAnimation();
|
||
},
|
||
),
|
||
),
|
||
],
|
||
|
||
const SizedBox(height: 14),
|
||
|
||
// Interactive Numeric Keypad (Directly maps to 3 d10 dice slots)
|
||
if (_showKeypad) ...[
|
||
Container(
|
||
padding: const EdgeInsets.all(12),
|
||
decoration: BoxDecoration(
|
||
color: isDark
|
||
? const Color(0xFF17192A)
|
||
: theme.colorScheme.surfaceContainerLow,
|
||
borderRadius: BorderRadius.circular(20),
|
||
border: Border.all(
|
||
color: const Color(0xFFFFD54F).withValues(alpha: 0.25),
|
||
),
|
||
),
|
||
child: Column(
|
||
children: [
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
const Row(
|
||
children: [
|
||
Icon(Icons.casino_outlined, size: 15, color: Color(0xFFFFD54F)),
|
||
SizedBox(width: 6),
|
||
Text(
|
||
'Ввод граней d10:',
|
||
style: TextStyle(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w700,
|
||
color: Color(0xFFFFD54F),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
Text(
|
||
_currentDigits.isEmpty
|
||
? 'Ожидание 3 цифр'
|
||
: (_currentDigits.length == 3
|
||
? 'Код: #$_currentDigits'
|
||
: 'Введено: ${_currentDigits.length} из 3'),
|
||
style: TextStyle(
|
||
fontSize: 11,
|
||
fontWeight: FontWeight.bold,
|
||
color: _currentDigits.length == 3
|
||
? const Color(0xFFFFD54F)
|
||
: theme.colorScheme.outline,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 10),
|
||
_buildKeypadRow(['1', '2', '3']),
|
||
const SizedBox(height: 8),
|
||
_buildKeypadRow(['4', '5', '6']),
|
||
const SizedBox(height: 8),
|
||
_buildKeypadRow(['7', '8', '9']),
|
||
const SizedBox(height: 8),
|
||
_buildKeypadRow(['C', '0', '⌫']),
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
],
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildKeypadRow(List<String> keys) {
|
||
return Row(
|
||
children: keys.map((key) {
|
||
final isSpecial = key == 'C' || key == '⌫';
|
||
final theme = Theme.of(context);
|
||
final isDark = theme.brightness == Brightness.dark;
|
||
|
||
return Expanded(
|
||
child: Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||
child: Material(
|
||
color: isSpecial
|
||
? const Color(0xFF2A2D4A)
|
||
: isDark
|
||
? const Color(0xFF20233A)
|
||
: theme.colorScheme.surface,
|
||
borderRadius: BorderRadius.circular(14),
|
||
elevation: 1,
|
||
child: InkWell(
|
||
onTap: () => _onKeypadTap(key),
|
||
borderRadius: BorderRadius.circular(14),
|
||
child: Container(
|
||
height: 46,
|
||
alignment: Alignment.center,
|
||
decoration: BoxDecoration(
|
||
borderRadius: BorderRadius.circular(14),
|
||
border: Border.all(
|
||
color: isSpecial
|
||
? const Color(0xFFFFD54F).withValues(alpha: 0.4)
|
||
: Colors.white10,
|
||
),
|
||
),
|
||
child: Text(
|
||
key,
|
||
style: TextStyle(
|
||
fontSize: 20,
|
||
fontWeight: FontWeight.bold,
|
||
color: isSpecial
|
||
? const Color(0xFFFFF9C4)
|
||
: isDark
|
||
? Colors.white
|
||
: Colors.black87,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}).toList(),
|
||
);
|
||
}
|
||
}
|