feat: complete academic symbol lexicon heal, 2-column Cooper extraction, and updated assets

This commit is contained in:
2026-10-09 15:26:58 +05:00
parent 7f91366201
commit 0b12e4583b
244 changed files with 611461 additions and 1617 deletions
+599
View File
@@ -0,0 +1,599 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../models/history_entry_model.dart';
import '../models/houses_session_model.dart';
import '../models/word_model.dart';
import '../services/ai_oracle_service.dart';
import '../widgets/gradient_star_badge.dart';
import 'symbol_detail_screen.dart';
import 'matrix_geometry_screen.dart';
import '../models/syncretic_session_model.dart';
import '../services/words_repository.dart';
class HouseChainsScreen extends StatefulWidget {
final HousesSession session;
const HouseChainsScreen({super.key, required this.session});
@override
State<HouseChainsScreen> createState() => _HouseChainsScreenState();
}
class _HouseChainsScreenState extends State<HouseChainsScreen> {
final _aiService = AiOracleService();
final Map<int, String> _chainAiInterpretations = {};
final Set<int> _loadingChains = {};
@override
void initState() {
super.initState();
_recordChainsToSyncreticSession(showToast: false);
}
Future<void> _recordChainsToSyncreticSession({bool showToast = false}) async {
final session = widget.session;
if (session.chains.isEmpty) return;
final chainItems = session.chains.map((chain) {
final pathStr = chain.steps.map((s) => '«${s.guest.word.toUpperCase()}»').join(' ➔ ');
final startWord = chain.startingHouse.houseWord;
final aiText = _chainAiInterpretations[chain.chainIndex];
var loreText = 'Траектория: ' + pathStr + '\n' + chain.description;
if (aiText != null && aiText.isNotEmpty) {
loreText += '\n\nТолкование Оракула:\n' + aiText;
}
return ReadingSignItem(
title: 'Цепочка #${chain.chainIndex}: ${chain.endTypeTitle}',
subtitle: 'Старт: Дом #${chain.startingHouse.houseIndex + 1} «${startWord.word}» • Шагов: ${chain.steps.length}',
symbol: chain.hasLoop ? '🔄' : (chain.endType == ChainEndType.archonThrone ? '👑' : '🔗'),
lore: loreText,
linkedWord: chain.steps.isNotEmpty ? chain.steps.last.guest : startWord,
meta: {
'chainIndex': chain.chainIndex,
'endType': chain.endType.name,
'stepsCount': chain.steps.length,
'path': pathStr,
},
);
}).toList();
await WordsRepository().addReadingToSyncreticSession(
SyncreticReading(
id: 'chains_${session.id}',
systemId: 'matrix_chains',
systemName: 'Матрица 33×30: Цепочки Домов',
systemIcon: '🔗',
timestamp: session.createdAt,
layoutTitle: '${session.chains.length} ${session.chains.length == 1 ? "цепочка" : (session.chains.length < 5 ? "цепочки" : "цепочек")}',
inputMethod: 'dice',
items: chainItems,
question: session.question,
),
question: session.question,
);
if (showToast && mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Цепочки домов (${session.chains.length}) сохранены в Синхроническую сессию дня',
style: const TextStyle(fontWeight: FontWeight.w600),
),
backgroundColor: const Color(0xFF1E2238),
behavior: SnackBarBehavior.floating,
),
);
}
}
Future<void> _interpretChain(HouseChain chain) async {
setState(() => _loadingChains.add(chain.chainIndex));
HapticFeedback.lightImpact();
final buffer = StringBuffer();
buffer.writeln('Ты — верховный оракул, читающий цепочки домов Ленорман в системе Оракула 999.');
buffer.writeln('Проанализируй путь перетекания силы в цепочке домов:');
buffer.writeln('ЦЕПОЧКА #${chain.chainIndex} (${chain.steps.length} шагов).');
buffer.writeln('ИТОГ ЦЕПОЧКИ: ${chain.description}.\n');
for (int i = 0; i < chain.steps.length; i++) {
final step = chain.steps[i];
final sector = widget.session.sectors[step.house.sectorIndex];
buffer.writeln(
'Шаг ${i + 1}: В Доме #${step.house.houseIndex + 1} «${step.house.houseWord.word.toUpperCase()}» (Архонт «${sector.archonWord.word.toUpperCase()}») выпал Гость «${step.guest.word.toUpperCase()}».',
);
if (step.isLoopClosing) {
buffer.writeln(' ➔ [ПЕТЛЯ ЗАМКНУЛАСЬ: возврат энергии в исходный дом]');
} else if (step.leadsToArchon) {
buffer.writeln(' ➔ [ВОСХОЖДЕНИЕ К АРХОНТУ: увенчание Престолом «${step.destinationArchon?.archonWord.word.toUpperCase()}»]');
} else if (step.computed) {
buffer.writeln(' ➔ [ВЫЧИСЛЕНО ИЗ ПОЛЯ, НЕ ЖРЕБИЙ: дом не был открыт]');
}
}
buffer.writeln('\nДай емкое и глубокое толкование: что запускает этот поток, где ключевой перелом и к какому исходу приводит замыкание цепи.');
try {
final res = await _aiService.interpretSession(
words: chain.steps
.map((s) => SessionWord.fromWordItem(
s.guest,
positionTitle: 'Дом «${s.house.houseWord.word}»',
))
.toList(),
question: widget.session.question ?? 'Цепочка домов судьбы',
);
if (mounted) {
setState(() {
_chainAiInterpretations[chain.chainIndex] = res;
_loadingChains.remove(chain.chainIndex);
});
_recordChainsToSyncreticSession(showToast: false);
}
} catch (e) {
if (mounted) {
setState(() {
_chainAiInterpretations[chain.chainIndex] = 'Связь с оракулом прервалась: $e';
_loadingChains.remove(chain.chainIndex);
});
}
}
}
void _openWordDossier(WordItem word) {
HapticFeedback.selectionClick();
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SymbolDetailScreen(word: word)),
);
}
void _copyChain(HouseChain chain) {
final buffer = StringBuffer();
buffer.writeln('🔗 ОРАКУЛ — ЦЕПОЧКА ДОМОВ #${chain.chainIndex}');
buffer.writeln('Итог: ${chain.description}\n');
for (int i = 0; i < chain.steps.length; i++) {
final s = chain.steps[i];
final archon = widget.session.sectors[s.house.sectorIndex].archonWord.word.toUpperCase();
buffer.writeln(
'${i + 1}. [Архонт: $archon] ➔ Дом «${s.house.houseWord.word.toUpperCase()}» ➔ Гость «${s.guest.word.toUpperCase()}»',
);
if (s.isLoopClosing) {
buffer.writeln(' 🔄 Цикл замкнулся!');
} else if (s.leadsToArchon) {
buffer.writeln(' 👑 Восхождение к Архонту «${s.destinationArchon?.archonWord.word.toUpperCase()}»');
} else if (s.computed) {
buffer.writeln(' ◎ Знак вычислен из поля, это не жребий.');
}
}
if (_chainAiInterpretations[chain.chainIndex] != null) {
buffer.writeln('\n🤖 Толкование Оракула:\n${_chainAiInterpretations[chain.chainIndex]}');
}
Clipboard.setData(ClipboardData(text: buffer.toString()));
HapticFeedback.selectionClick();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Цепочка домов скопирована в буфер'),
behavior: SnackBarBehavior.floating,
),
);
}
@override
Widget build(BuildContext context) {
final chains = widget.session.chains;
return Scaffold(
backgroundColor: const Color(0xFF0A0C16),
appBar: AppBar(
title: const Row(
children: [
GradientStar(size: 20),
SizedBox(width: 8),
Text('Цепочки Домов', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
],
),
actions: [
IconButton(
tooltip: 'Записать цепочки в Синхроническую сессию дня',
icon: const Icon(Icons.bookmark_add_rounded, color: Color(0xFFFFD54F)),
onPressed: () => _recordChainsToSyncreticSession(showToast: true),
),
],
),
body: chains.isEmpty
? const Center(
child: Text(
'В данном раскладе нет открытых цепочек',
style: TextStyle(color: Color(0xFFB0BEC5)),
),
)
: ListView.builder(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 40),
itemCount: chains.length + 1,
itemBuilder: (context, index) {
if (index == 0) {
return _buildHeaderInfo();
}
final chain = chains[index - 1];
return _buildChainCard(chain);
},
),
);
}
Widget _buildHeaderInfo() {
return Container(
margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF151829),
borderRadius: BorderRadius.circular(18),
border: Border.all(color: const Color(0xFFFFD54F).withValues(alpha: 0.3)),
),
child: const Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(Icons.hub_outlined, color: Color(0xFFFFD54F), size: 24),
SizedBox(width: 12),
Expanded(
child: Text(
'Цепочки домов показывают траекторию судьбы: гость из первого дома ведет в собственную обитель на поле 33×30, гость следующего дома ведет дальше — пока энергия не замкнется в петлю судьбы или не взойдет к Престолу Архонта.',
style: TextStyle(color: Color(0xFFCFD8DC), fontSize: 13, height: 1.45),
),
),
],
),
);
}
Widget _buildChainCard(HouseChain chain) {
final isInterpreting = _loadingChains.contains(chain.chainIndex);
final aiText = _chainAiInterpretations[chain.chainIndex];
Color statusColor;
IconData statusIcon;
String statusTitle;
switch (chain.endType) {
case ChainEndType.loop:
statusColor = const Color(0xFFE57373);
statusIcon = Icons.sync_rounded;
statusTitle = 'Петля Судьбы (Замкнутый узел)';
break;
case ChainEndType.archonThrone:
statusColor = const Color(0xFFFFD54F);
statusIcon = Icons.shield_rounded;
statusTitle = 'Восхождение к Престолу Архонта';
break;
case ChainEndType.fieldVector:
statusColor = const Color(0xFF64B5F6);
statusIcon = Icons.trending_flat_rounded;
statusTitle = 'Вектор в поле (Открытый луч)';
break;
}
return Container(
margin: const EdgeInsets.only(bottom: 20),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF131627),
borderRadius: BorderRadius.circular(22),
border: Border.all(
color: chain.hasLoop
? const Color(0xFFFFD54F).withValues(alpha: 0.5)
: const Color(0xFF2A2D4A),
width: chain.hasLoop ? 1.5 : 1.0,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header: Chain index + Status Badge
Row(
children: [
Text(
'ЦЕПОЧКА #${chain.chainIndex}',
style: const TextStyle(
color: Color(0xFFFFD54F),
fontWeight: FontWeight.w900,
fontSize: 16,
letterSpacing: 0.8,
),
),
const Spacer(),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: statusColor.withValues(alpha: 0.18),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: statusColor, width: 1.0),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(statusIcon, size: 14, color: statusColor),
const SizedBox(width: 4),
Text(
statusTitle,
style: TextStyle(
color: statusColor,
fontSize: 11,
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
const SizedBox(height: 6),
Text(
chain.description,
style: const TextStyle(color: Color(0xFFB0BEC5), fontSize: 12),
),
const SizedBox(height: 16),
// Steps list
for (int i = 0; i < chain.steps.length; i++) ...[
_buildStepRow(chain.steps[i], i + 1),
if (i < chain.steps.length - 1)
Padding(
padding: const EdgeInsets.only(left: 20, top: 4, bottom: 4),
child: Row(
children: [
const Icon(Icons.arrow_downward_rounded, size: 16, color: Color(0xFFFFD54F)),
const SizedBox(width: 8),
Text(
'переход в обитель знака «${chain.steps[i].guest.word.toUpperCase()}» на поле',
style: const TextStyle(color: Color(0xFF78909C), fontSize: 10, fontStyle: FontStyle.italic),
),
],
),
),
],
const SizedBox(height: 16),
// AI Interpretation if exists
if (aiText != null) ...[
Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xFF1E2238),
borderRadius: BorderRadius.circular(14),
border: Border.all(color: const Color(0xFFFFD54F).withValues(alpha: 0.4)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
children: [
Icon(Icons.auto_awesome_rounded, color: Color(0xFFFFD54F), size: 16),
SizedBox(width: 6),
Text(
'Толкование цепочки Оракулом:',
style: TextStyle(
color: Color(0xFFFFD54F),
fontWeight: FontWeight.bold,
fontSize: 13,
),
),
],
),
const SizedBox(height: 8),
Text(
aiText,
style: const TextStyle(color: Color(0xFFECEFF1), fontSize: 13, height: 1.45),
),
],
),
),
const SizedBox(height: 12),
],
// Action Buttons
Row(
children: [
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2A2D4A),
foregroundColor: const Color(0xFFFFD54F),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: const BorderSide(color: Color(0xFFFFD54F), width: 0.8),
),
),
onPressed: isInterpreting ? null : () => _interpretChain(chain),
icon: isInterpreting
? const SizedBox(
width: 14,
height: 14,
child: CircularProgressIndicator(strokeWidth: 2, color: Color(0xFFFFD54F)),
)
: const Icon(Icons.auto_awesome, size: 16),
label: Text(
aiText == null ? 'Трактовать (AI)' : 'Обновить (AI)',
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.bold),
),
),
const SizedBox(width: 8),
OutlinedButton.icon(
style: OutlinedButton.styleFrom(
foregroundColor: const Color(0xFF00E5FF),
side: const BorderSide(color: Color(0xFF00E5FF), width: 0.8),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MatrixGeometryScreen(
session: widget.session,
initialChainIndex: chain.chainIndex,
),
),
);
},
icon: const Icon(Icons.polyline_rounded, size: 15),
label: const Text('Узор на поле', style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold)),
),
const Spacer(),
IconButton(
tooltip: 'Скопировать цепочку',
icon: const Icon(Icons.copy_rounded, size: 18, color: Color(0xFF9FA8DA)),
onPressed: () => _copyChain(chain),
),
],
),
],
),
);
}
Widget _buildStepRow(HouseChainStep step, int order) {
final sector = widget.session.sectors[step.house.sectorIndex];
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
decoration: BoxDecoration(
color: const Color(0xFF181C33),
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: step.isLoopClosing
? const Color(0xFFE57373)
: (step.leadsToArchon ? const Color(0xFFFFD54F) : sector.sectorColor.withValues(alpha: 0.3)),
width: (step.isLoopClosing || step.leadsToArchon) ? 1.5 : 1.0,
),
),
child: Row(
children: [
// Order circle
Container(
width: 24,
height: 24,
decoration: BoxDecoration(
color: sector.sectorColor.withValues(alpha: 0.2),
shape: BoxShape.circle,
border: Border.all(color: sector.sectorColor),
),
child: Center(
child: Text(
'$order',
style: TextStyle(
color: sector.sectorColor,
fontWeight: FontWeight.w900,
fontSize: 11,
),
),
),
),
const SizedBox(width: 12),
// Content
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// House title
InkWell(
onTap: () => _openWordDossier(step.house.houseWord),
child: Row(
children: [
Text(
'Дом #${(step.house.houseIndex + 1).toString().padLeft(3, '0')}: «${step.house.houseWord.word.toUpperCase()}»',
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.w900,
fontSize: 13,
),
),
const SizedBox(width: 6),
Text(
'(Архонт: ${sector.archonWord.word})',
style: TextStyle(
color: sector.sectorColor,
fontSize: 11,
fontWeight: FontWeight.w600,
),
),
],
),
),
const SizedBox(height: 3),
// Guest title
InkWell(
onTap: () => _openWordDossier(step.guest),
child: Row(
children: [
const Icon(Icons.auto_awesome_rounded, size: 13, color: Color(0xFFFFD54F)),
const SizedBox(width: 4),
Text(
step.computed
? 'Вычислен из поля, не жребий: «${step.guest.word.toUpperCase()}»'
: 'В него пришел Гость: «${step.guest.word.toUpperCase()}»',
style: const TextStyle(
color: Color(0xFFFFD54F),
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
],
),
),
// Special termination notices
if (step.isLoopClosing) ...[
const SizedBox(height: 4),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: const Color(0xFFE57373).withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(6),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.sync_rounded, size: 12, color: Color(0xFFE57373)),
SizedBox(width: 4),
Text(
'🔄 Узел замкнулся: энергия возвращается в исходный дом!',
style: TextStyle(color: Color(0xFFEF9A9A), fontSize: 11, fontWeight: FontWeight.bold),
),
],
),
),
] else if (step.leadsToArchon) ...[
const SizedBox(height: 4),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: const Color(0xFFFFD54F).withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(6),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.shield_rounded, size: 12, color: Color(0xFFFFD54F)),
const SizedBox(width: 4),
Text(
'👑 Восхождение к Престолу Архонта «${step.destinationArchon?.archonWord.word.toUpperCase()}»!',
style: const TextStyle(color: Color(0xFFFFF9C4), fontSize: 11, fontWeight: FontWeight.bold),
),
],
),
),
],
],
),
),
],
),
);
}
}