feat: 3xd10 cleromancy input flow, animated dice roll sequence, and cleromancy guide dialog
This commit is contained in:
@@ -3,6 +3,8 @@ 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';
|
||||
|
||||
@@ -18,16 +20,17 @@ class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
||||
final _searchController = TextEditingController();
|
||||
final _questionController = TextEditingController();
|
||||
final _focusNode = FocusNode();
|
||||
final _diceRollerKey = GlobalKey<D10DiceRollerWidgetState>();
|
||||
|
||||
WordItem? _currentWord;
|
||||
String? _activeHistoryId;
|
||||
List<WordItem> _searchResults = [];
|
||||
bool _showKeypad = true;
|
||||
String _currentDigits = ''; // Tracks 0..3 digits input for d10 dice cleromancy
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Do NOT show word by default until user interacts or starts a session
|
||||
_searchController.addListener(_onSearchChanged);
|
||||
}
|
||||
|
||||
@@ -45,17 +48,38 @@ class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
||||
if (query.isEmpty) {
|
||||
setState(() {
|
||||
_searchResults = [];
|
||||
_currentDigits = '';
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final results = _repo.search(query);
|
||||
setState(() {
|
||||
_searchResults = results;
|
||||
if (results.isNotEmpty) {
|
||||
_setWord(results.first, recordHistory: true);
|
||||
final isNumeric = int.tryParse(query) != null;
|
||||
|
||||
if (isNumeric) {
|
||||
setState(() {
|
||||
_currentDigits = query;
|
||||
});
|
||||
// In Cleromancy, exactly 3 digits (d10x3) are required to resolve a number 000..999
|
||||
if (query.length >= 3) {
|
||||
final num = int.tryParse(query);
|
||||
if (num != null && num <= 999) {
|
||||
final word = _repo.getWordBySessionNumber(num);
|
||||
if (word != null) {
|
||||
_setWord(word, recordHistory: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Text search by word
|
||||
final results = _repo.search(query);
|
||||
setState(() {
|
||||
_searchResults = results;
|
||||
_currentDigits = '';
|
||||
if (results.isNotEmpty) {
|
||||
_setWord(results.first, recordHistory: true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _setWord(WordItem word, {bool updateText = false, bool recordHistory = true}) async {
|
||||
@@ -71,7 +95,7 @@ class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
||||
sessionNumber: word.sessionNumber,
|
||||
partOfSpeech: word.partOfSpeech,
|
||||
symbolicMeaning: word.description,
|
||||
readingType: 'Жребий номера',
|
||||
readingType: 'Клеромантия (3× d10)',
|
||||
question: question.isNotEmpty ? question : null,
|
||||
);
|
||||
await _repo.addHistoryEntry(entry);
|
||||
@@ -80,8 +104,9 @@ class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
||||
setState(() {
|
||||
_currentWord = word;
|
||||
_activeHistoryId = newHistId ?? _activeHistoryId;
|
||||
_currentDigits = word.formattedSessionNumber;
|
||||
if (updateText) {
|
||||
_searchController.text = word.sessionNumber.toString();
|
||||
_searchController.text = word.formattedSessionNumber;
|
||||
}
|
||||
});
|
||||
HapticFeedback.selectionClick();
|
||||
@@ -93,6 +118,7 @@ class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
||||
|
||||
if (val == 'C') {
|
||||
_searchController.clear();
|
||||
setState(() => _currentDigits = '');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -106,17 +132,11 @@ class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
||||
return;
|
||||
}
|
||||
|
||||
// Append digit
|
||||
// In Cleromancy, append digit up to 3 digits
|
||||
if (currentText.length >= 3) {
|
||||
_searchController.text = val;
|
||||
} else {
|
||||
final newText = currentText + val;
|
||||
final num = int.tryParse(newText);
|
||||
if (num != null && num <= 999) {
|
||||
_searchController.text = newText;
|
||||
} else {
|
||||
_searchController.text = val;
|
||||
}
|
||||
_searchController.text = currentText + val;
|
||||
}
|
||||
|
||||
_searchController.selection = TextSelection.fromPosition(
|
||||
@@ -124,9 +144,11 @@ class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
void _startNewSessionDraw() {
|
||||
final rnd = _repo.getRandomWord();
|
||||
_setWord(rnd, updateText: true, recordHistory: true);
|
||||
void _onVirtualRollCompleted(int finalNumber) {
|
||||
final word = _repo.getWordBySessionNumber(finalNumber);
|
||||
if (word != null) {
|
||||
_setWord(word, updateText: true, recordHistory: true);
|
||||
}
|
||||
}
|
||||
|
||||
void _resetToWelcome() {
|
||||
@@ -134,6 +156,7 @@ class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
||||
_currentWord = null;
|
||||
_searchController.clear();
|
||||
_searchResults = [];
|
||||
_currentDigits = '';
|
||||
});
|
||||
}
|
||||
|
||||
@@ -151,11 +174,7 @@ class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
||||
|
||||
void _reshuffleSession() {
|
||||
_repo.reshuffleSessionNumbers();
|
||||
setState(() {
|
||||
_currentWord = null;
|
||||
_searchController.clear();
|
||||
_searchResults = [];
|
||||
});
|
||||
_resetToWelcome();
|
||||
HapticFeedback.heavyImpact();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
@@ -200,6 +219,11 @@ class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
tooltip: 'Инструкция: Клеромантия и кубики d10',
|
||||
icon: const Icon(Icons.help_outline_rounded, color: Color(0xFFFFD54F)),
|
||||
onPressed: () => CleromancyGuideDialog.show(context),
|
||||
),
|
||||
if (_currentWord != null)
|
||||
IconButton(
|
||||
tooltip: 'Новый вопрос / Сброс',
|
||||
@@ -207,7 +231,7 @@ class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
||||
onPressed: _resetToWelcome,
|
||||
),
|
||||
IconButton(
|
||||
tooltip: 'Перетасовать числа (#000..#999)',
|
||||
tooltip: 'Перетасовать матрицу (#000..#999)',
|
||||
icon: const Icon(Icons.shuffle_rounded),
|
||||
onPressed: _reshuffleSession,
|
||||
),
|
||||
@@ -231,7 +255,7 @@ class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
||||
controller: _questionController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Ваш вопрос или намерение (необязательно)',
|
||||
hintText: 'Например: В чем скрытый потенциал ситуации?',
|
||||
hintText: 'Например: В чем корень текущей ситуации?',
|
||||
prefixIcon: const Icon(Icons.help_outline_rounded, color: Color(0xFFFFD54F), size: 20),
|
||||
suffixIcon: _questionController.text.isNotEmpty
|
||||
? IconButton(
|
||||
@@ -263,9 +287,20 @@ class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// D10 Dice Roller Slots Widget
|
||||
D10DiceRollerWidget(
|
||||
key: _diceRollerKey,
|
||||
digits: _currentDigits,
|
||||
isRolling: false,
|
||||
onRollPressed: () {},
|
||||
onRollCompleted: _onVirtualRollCompleted,
|
||||
),
|
||||
|
||||
const SizedBox(height: 10),
|
||||
|
||||
// Search & Number Input Bar
|
||||
// Search & Direct Number Input Bar
|
||||
TextField(
|
||||
controller: _searchController,
|
||||
focusNode: _focusNode,
|
||||
@@ -283,6 +318,7 @@ class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
||||
onPressed: () {
|
||||
_searchController.clear();
|
||||
_focusNode.unfocus();
|
||||
setState(() => _currentDigits = '');
|
||||
},
|
||||
)
|
||||
: null,
|
||||
@@ -312,8 +348,8 @@ class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
||||
),
|
||||
),
|
||||
|
||||
// Search matches dropdown list
|
||||
if (_searchResults.length > 1 && _searchController.text.isNotEmpty) ...[
|
||||
// Search matches dropdown list (for text search)
|
||||
if (_searchResults.length > 1 && _searchController.text.isNotEmpty && int.tryParse(_searchController.text) == null) ...[
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxHeight: 140),
|
||||
@@ -357,7 +393,7 @@ class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
_setWord(item, updateText: false, recordHistory: true);
|
||||
_setWord(item, updateText: true, recordHistory: true);
|
||||
},
|
||||
);
|
||||
},
|
||||
@@ -371,7 +407,7 @@ class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
||||
if (_currentWord == null) ...[
|
||||
// Welcome Initiation Card
|
||||
Container(
|
||||
padding: const EdgeInsets.all(22),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
@@ -386,58 +422,47 @@ class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
||||
color: const Color(0xFFFFD54F).withValues(alpha: 0.35),
|
||||
width: 1.2,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0xFFFFD54F).withValues(alpha: 0.1),
|
||||
blurRadius: 16,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const GradientStar(size: 44),
|
||||
const SizedBox(height: 14),
|
||||
const GradientStar(size: 38),
|
||||
const SizedBox(height: 12),
|
||||
const Text(
|
||||
'СВЯЩЕННЫЙ ЖРЕБИЙ ОРАКУЛА',
|
||||
'СВЯЩЕННЫЙ ЖРЕБИЙ КЛЕРОМАНТИИ',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w900,
|
||||
letterSpacing: 1.0,
|
||||
letterSpacing: 0.8,
|
||||
color: Color(0xFFFFF9C4),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Сформулируйте в мыслях или запишите сверху ваш вопрос, затем нажмите кнопку или наберите номер от 000 до 999 для раскрытия символического ключа.',
|
||||
'Сформулируйте вопрос, затем совершите 3 броска кубика d10 кнопкой выше или введите 3 выпавших числа (Сотни, Десятки, Единицы).',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
height: 1.45,
|
||||
height: 1.4,
|
||||
color: theme.colorScheme.onSurface.withValues(alpha: 0.85),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
FilledButton.icon(
|
||||
onPressed: _startNewSessionDraw,
|
||||
icon: const GradientStar(size: 20),
|
||||
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(
|
||||
fontWeight: FontWeight.w900,
|
||||
letterSpacing: 0.5,
|
||||
color: Color(0xFFFFD54F),
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12.5,
|
||||
),
|
||||
),
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF2A2D4A),
|
||||
foregroundColor: const Color(0xFFFFF9C4),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
||||
style: OutlinedButton.styleFrom(
|
||||
side: const BorderSide(color: Color(0xFFFFD54F), width: 1),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
side: const BorderSide(
|
||||
color: Color(0xFFFFD54F),
|
||||
width: 1.2,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -467,7 +492,9 @@ class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
||||
historyEntryId: _activeHistoryId,
|
||||
onNext: _goToNext,
|
||||
onPrevious: _goToPrevious,
|
||||
onRandom: _startNewSessionDraw,
|
||||
onRandom: () {
|
||||
_diceRollerKey.currentState?.triggerRollAnimation();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -494,10 +521,10 @@ class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
||||
children: [
|
||||
const Row(
|
||||
children: [
|
||||
GradientStar(size: 14),
|
||||
Icon(Icons.casino_outlined, size: 15, color: Color(0xFFFFD54F)),
|
||||
SizedBox(width: 6),
|
||||
Text(
|
||||
'Случайный жребий числа:',
|
||||
'Ввод граней d10 (3 цифры):',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
@@ -507,10 +534,15 @@ class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
||||
],
|
||||
),
|
||||
Text(
|
||||
'000 — 999',
|
||||
_currentDigits.length == 3
|
||||
? 'Код: #$_currentDigits'
|
||||
: 'Введено: ${_currentDigits.length} из 3',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: theme.colorScheme.outline,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: _currentDigits.length == 3
|
||||
? const Color(0xFFFFD54F)
|
||||
: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user