Files
999/lib/screens/iching_divination_screen.dart
T

526 lines
18 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import '../widgets/divination_instruction_modal.dart';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../models/syncretic_session_model.dart';
import '../models/tradition_model.dart';
import '../models/word_model.dart';
import '../services/traditions_repository.dart';
import '../services/words_repository.dart';
class IChingDivinationScreen extends StatefulWidget {
final String? initialQuestion;
const IChingDivinationScreen({super.key, this.initialQuestion});
@override
State<IChingDivinationScreen> createState() => _IChingDivinationScreenState();
}
class _IChingDivinationScreenState extends State<IChingDivinationScreen> with SingleTickerProviderStateMixin {
late TabController _tabController;
final _questionController = TextEditingController();
final _repo = WordsRepository();
List<TraditionStation> _hexagrams = [];
// Six lines (from bottom line 1 to top line 6). Values: 6 (old yin), 7 (young yang), 8 (young yin), 9 (old yang)
List<int> _lines = [];
bool _isTossingCoins = false;
List<int> _currentCoins = [3, 3, 3]; // 3 coins (2=tail, 3=head)
TraditionStation? _resultStation;
bool _savedToSession = false;
@override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
if (widget.initialQuestion != null) {
_questionController.text = widget.initialQuestion!;
}
_loadHexagrams();
}
void _loadHexagrams() {
final canon = TraditionsRepository().byId('iching');
if (canon != null) {
_hexagrams = canon.stations;
}
}
@override
void dispose() {
_tabController.dispose();
_questionController.dispose();
super.dispose();
}
Future<void> _tossCoinsStep() async {
if (_lines.length >= 6) return;
setState(() => _isTossingCoins = true);
HapticFeedback.mediumImpact();
final rnd = Random();
for (int i = 0; i < 6; i++) {
await Future.delayed(const Duration(milliseconds: 50));
if (mounted) {
setState(() {
_currentCoins = [
rnd.nextBool() ? 3 : 2,
rnd.nextBool() ? 3 : 2,
rnd.nextBool() ? 3 : 2,
];
});
}
}
final finalCoins = [
rnd.nextBool() ? 3 : 2,
rnd.nextBool() ? 3 : 2,
rnd.nextBool() ? 3 : 2,
];
final sum = finalCoins.reduce((a, b) => a + b); // 6, 7, 8, or 9
if (mounted) {
setState(() {
_currentCoins = finalCoins;
_lines.add(sum);
_isTossingCoins = false;
if (_lines.length == 6) {
_resolveHexagram('dice');
}
});
HapticFeedback.heavyImpact();
}
}
void _addLineManually(int val) {
if (_lines.length >= 6) return;
setState(() {
_lines.add(val);
if (_lines.length == 6) {
_resolveHexagram('manual');
}
});
HapticFeedback.selectionClick();
}
void _resetLines() {
setState(() {
_lines.clear();
_resultStation = null;
_savedToSession = false;
});
}
void _resolveHexagram(String inputMethod) {
if (_hexagrams.isEmpty || _lines.length < 6) return;
// Map 6 lines to binary / trigrams
// Lower trigram: lines 0, 1, 2
// Upper trigram: lines 3, 4, 5
// For now find closest matching hexagram in canon
final hexIndex = (_lines.fold(0, (acc, val) => acc + val) % _hexagrams.length);
final station = _hexagrams[hexIndex];
setState(() {
_resultStation = station;
});
_saveToSession(inputMethod);
}
Future<void> _saveToSession(String inputMethod) async {
if (_resultStation == null) return;
final st = _resultStation!;
WordItem? linkedWord;
if (st.primary != null) {
linkedWord = _repo.getWordById(st.primary!.id) ?? _repo.getWordByName(st.primary!.word);
}
final linesStr = _lines.join('-');
final item = ReadingSignItem(
title: st.name,
subtitle: 'Линии: $linesStr',
symbol: st.symbol,
lore: st.lore,
linkedWord: linkedWord,
);
final reading = SyncreticReading(
id: 'reading_${DateTime.now().millisecondsSinceEpoch}',
systemId: 'iching',
systemName: 'И Цзин (Книга Перемен)',
systemIcon: '☯️',
timestamp: DateTime.now(),
layoutTitle: '6 линий: [$linesStr]',
inputMethod: inputMethod,
items: [item],
question: _questionController.text.trim(),
);
await _repo.addReadingToSyncreticSession(reading, question: _questionController.text.trim());
if (mounted) {
setState(() => _savedToSession = true);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Гексаграмма «${st.name}» добавлена в сессию дня!'),
backgroundColor: const Color(0xFF2E7D32),
behavior: SnackBarBehavior.floating,
duration: const Duration(seconds: 2),
),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFF0C0E1A),
appBar: AppBar(
title: const Row(
children: [
Text('☯️', style: TextStyle(fontSize: 22)),
SizedBox(width: 8),
Text('И Цзин (3 монеты × 6)', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 17)),
],
),
actions: [
IconButton(
tooltip: 'Инструкция по И Цзин',
icon: const Icon(Icons.help_outline_rounded, color: Color(0xFFFFD54F)),
onPressed: () => DivinationInstructionModal.show(context, 'iching'),
),
if (_lines.isNotEmpty)
IconButton(
tooltip: 'Сбросить черты',
icon: const Icon(Icons.refresh_rounded),
onPressed: _resetLines,
),
],
bottom: TabBar(
controller: _tabController,
indicatorColor: const Color(0xFFFFD54F),
tabs: const [
Tab(icon: Icon(Icons.toll_rounded, size: 18), text: 'Бросок 3 монет'),
Tab(icon: Icon(Icons.touch_app_rounded, size: 18), text: 'Ввод черт (6..9)'),
],
),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextField(
controller: _questionController,
style: const TextStyle(color: Colors.white, fontSize: 14),
decoration: InputDecoration(
hintText: 'Вопрос к Книге Перемен...',
hintStyle: const TextStyle(color: Color(0xFF8C93B8), fontSize: 13),
prefixIcon: const Icon(Icons.help_outline_rounded, color: Color(0xFFFFD54F), size: 18),
filled: true,
fillColor: const Color(0xFF151829),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(14)),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(color: Color(0xFF2A2D4A)),
),
),
),
const SizedBox(height: 16),
// Hexagram Visualizer Box (bottom to top: line 1 at bottom, line 6 at top)
_buildHexagramVisualizer(),
const SizedBox(height: 16),
// Controls Tabs
SizedBox(
height: 220,
child: TabBarView(
controller: _tabController,
children: [
_buildCoinTossTab(),
_buildManualLinesTab(),
],
),
),
const SizedBox(height: 16),
if (_resultStation != null) _buildResultCard(),
],
),
),
);
}
Widget _buildHexagramVisualizer() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF151829),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: const Color(0xFF2A2D4A)),
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'ПОСТРОЕНИЕ ГЕКСАГРАММЫ (${_lines.length}/6)',
style: const TextStyle(color: Color(0xFFFFD54F), fontSize: 11, fontWeight: FontWeight.bold),
),
if (_lines.length == 6)
const Text('Завершена ✓', style: TextStyle(color: Color(0xFF81C784), fontSize: 11, fontWeight: FontWeight.bold)),
],
),
const SizedBox(height: 12),
// Lines from top (line 6) to bottom (line 1)
for (int i = 5; i >= 0; i--)
Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
SizedBox(
width: 28,
child: Text(
'${i + 1}.',
style: TextStyle(
color: (i < _lines.length) ? const Color(0xFFFFD54F) : const Color(0xFF5C6285),
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
),
Expanded(
child: (i < _lines.length)
? _renderLineGraphic(_lines[i])
: Container(
height: 8,
decoration: BoxDecoration(
color: const Color(0xFF1E2238),
borderRadius: BorderRadius.circular(4),
),
),
),
const SizedBox(width: 8),
SizedBox(
width: 24,
child: Text(
(i < _lines.length) ? '${_lines[i]}' : '',
style: const TextStyle(color: Color(0xFF90A4AE), fontSize: 11, fontWeight: FontWeight.bold),
),
),
],
),
),
],
),
);
}
Widget _renderLineGraphic(int val) {
// 7 = young yang (solid)
// 9 = old yang (solid with circle/mutating)
// 8 = young yin (broken)
// 6 = old yin (broken with X/mutating)
final isYang = (val == 7 || val == 9);
final isMutating = (val == 6 || val == 9);
final color = isMutating ? const Color(0xFFFF8A65) : const Color(0xFFFFD54F);
if (isYang) {
return Container(
height: 10,
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(5),
),
);
} else {
// Broken line
return Row(
children: [
Expanded(
child: Container(
height: 10,
decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(5)),
),
),
const SizedBox(width: 14),
Expanded(
child: Container(
height: 10,
decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(5)),
),
),
],
);
}
}
Widget _buildCoinTossTab() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF181C30),
borderRadius: BorderRadius.circular(16),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
for (final c in _currentCoins)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Container(
width: 44,
height: 44,
alignment: Alignment.center,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xFF4A3E1E),
border: Border.all(color: const Color(0xFFFFD54F), width: 1.5),
),
child: Text(
c == 3 ? 'О (3)' : 'Р (2)',
style: const TextStyle(color: Color(0xFFFFF9C4), fontSize: 12, fontWeight: FontWeight.bold),
),
),
),
],
),
const SizedBox(height: 16),
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFFFD54F),
foregroundColor: Colors.black,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
),
onPressed: (_lines.length >= 6 || _isTossingCoins) ? null : _tossCoinsStep,
icon: const Icon(Icons.toll_rounded, size: 18),
label: Text(
_lines.length >= 6
? 'Все 6 линий брошены'
: (_isTossingCoins ? 'Монеты звенят...' : 'Бросить 3 монеты (линия ${_lines.length + 1})'),
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
],
),
);
}
Widget _buildManualLinesTab() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF181C30),
borderRadius: BorderRadius.circular(16),
),
child: Column(
children: [
Text(
'Нажмите выпавшую сумму броска со стола (${_lines.length + 1}-я черта):',
style: const TextStyle(color: Color(0xFF8C93B8), fontSize: 11),
),
const SizedBox(height: 12),
Row(
children: [
_buildManualLineButton('6 (Старая Инь ✕)', 6, Colors.redAccent),
const SizedBox(width: 8),
_buildManualLineButton('7 (Молодая Ян ⚊)', 7, Colors.amber),
],
),
const SizedBox(height: 8),
Row(
children: [
_buildManualLineButton('8 (Молодая Инь ⚋)', 8, Colors.blueAccent),
const SizedBox(width: 8),
_buildManualLineButton('9 (Старая Ян ◯)', 9, Colors.deepOrangeAccent),
],
),
],
),
);
}
Widget _buildManualLineButton(String label, int val, Color color) {
return Expanded(
child: OutlinedButton(
style: OutlinedButton.styleFrom(
foregroundColor: color,
side: BorderSide(color: color.withValues(alpha: 0.5)),
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
),
onPressed: _lines.length >= 6 ? null : () => _addLineManually(val),
child: Text(label, style: const TextStyle(fontSize: 11.5, fontWeight: FontWeight.bold)),
),
);
}
Widget _buildResultCard() {
final st = _resultStation!;
return Container(
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
color: const Color(0xFF181C30),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: const Color(0xFFFFD54F).withValues(alpha: 0.5)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 52,
height: 52,
alignment: Alignment.center,
decoration: BoxDecoration(
color: const Color(0xFF232742),
borderRadius: BorderRadius.circular(14),
border: Border.all(color: const Color(0xFFFFD54F)),
),
child: Text(st.symbol, style: const TextStyle(fontSize: 28, color: Color(0xFFFFD54F))),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(st.name, style: const TextStyle(color: Color(0xFFFFF9C4), fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 4),
Text(st.group, style: const TextStyle(color: Color(0xFF80DEEA), fontSize: 12)),
],
),
),
],
),
const SizedBox(height: 14),
const Divider(color: Color(0xFF2A2D4A)),
const SizedBox(height: 10),
Text(st.lore, style: const TextStyle(color: Color(0xFFE0E2EC), fontSize: 14, height: 1.5)),
if (st.primary != null) ...[
const SizedBox(height: 14),
Row(
children: [
const Icon(Icons.link_rounded, size: 16, color: Color(0xFFFFD54F)),
const SizedBox(width: 6),
Text(
'Связан со знаком 999: #${st.primary!.id} «${st.primary!.word.toUpperCase()}»',
style: const TextStyle(color: Color(0xFFFFD54F), fontSize: 12, fontWeight: FontWeight.bold),
),
],
),
],
],
),
);
}
}