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
+659
View File
@@ -0,0 +1,659 @@
import '../widgets/divination_instruction_modal.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../services/words_repository.dart';
import '../widgets/gradient_star_badge.dart';
import 'home_search_screen.dart';
import 'houses_field_screen.dart';
import 'runes_divination_screen.dart';
import 'lenormand_divination_screen.dart';
import 'tarot_divination_screen.dart';
import 'iching_divination_screen.dart';
import 'cross_system_comparison_screen.dart';
import 'numerology_divination_screen.dart';
import 'geomancy_divination_screen.dart';
import 'ogham_divination_screen.dart';
import '../services/word_of_the_day_service.dart';
import 'word_of_the_day_screen.dart';
class DivinationHubScreen extends StatefulWidget {
const DivinationHubScreen({super.key});
@override
State<DivinationHubScreen> createState() => _DivinationHubScreenState();
}
class _DivinationHubScreenState extends State<DivinationHubScreen> {
final _repo = WordsRepository();
final _wordOfDayService = WordOfTheDayService();
void _openComparisonScreen() {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => CrossSystemComparisonScreen(
session: _repo.currentSyncreticSession,
),
),
).then((_) => setState(() {}));
}
void _showNewSessionDialog() {
final controller = TextEditingController();
showDialog(
context: context,
builder: (ctx) => AlertDialog(
backgroundColor: const Color(0xFF151829),
title: const Row(
children: [
GradientStar(size: 20),
SizedBox(width: 8),
Text('Новая Сессия Дня', style: TextStyle(color: Color(0xFFFFF9C4), fontSize: 17, fontWeight: FontWeight.bold)),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Начните новую синхронистическую сессию по новой теме или вопросу. Предыдущая сессия сохранится в архиве.',
style: TextStyle(color: Color(0xFF8C93B8), fontSize: 13, height: 1.4),
),
const SizedBox(height: 14),
TextField(
controller: controller,
style: const TextStyle(color: Colors.white, fontSize: 14),
decoration: InputDecoration(
hintText: 'Главный вопрос / тема сессии...',
hintStyle: const TextStyle(color: Color(0xFF8C93B8), fontSize: 12),
filled: true,
fillColor: const Color(0xFF1E2238),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
child: const Text('Отмена'),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFFFD54F),
foregroundColor: Colors.black,
),
onPressed: () async {
Navigator.pop(ctx);
await _repo.startNewSyncreticSession(question: controller.text.trim());
setState(() {});
HapticFeedback.mediumImpact();
},
child: const Text('Начать'),
),
],
),
);
}
Widget _buildWordOfDayHeroBanner() {
final now = DateTime.now();
final rec = _wordOfDayService.getRecord(now);
final isTriptych = rec.isTriptychMode;
final singleWord = _wordOfDayService.getWordForDate(now, phase: 'main');
final triptych = _wordOfDayService.getTriptychWordsForDate(now);
final streak = _wordOfDayService.calculateStreak();
final isRevealed = isTriptych ? rec.isAnyRevealed : rec.isRevealed;
final isDone = isTriptych ? rec.isDayStepCompleted : rec.isStepCompleted;
String titleTag = isTriptych ? 'ТРИПТИХ СУТОК (3 СЛОВА)' : 'СЛОВО ДНЯ • КАЛЕНДАРЬ';
String subtitle;
if (isTriptych) {
if (rec.areAllTriptychRevealed) {
subtitle = '🌅 ${triptych.morning.word.toUpperCase()} • ⚡ ${triptych.day.word.toUpperCase()} • 🌙 ${triptych.evening.word.toUpperCase()}';
} else if (rec.isAnyRevealed) {
final m = rec.isMorningRevealed ? triptych.morning.word.toUpperCase() : '•••';
final d = rec.isDayRevealed ? triptych.day.word.toUpperCase() : '•••';
final e = rec.isEveningRevealed ? triptych.evening.word.toUpperCase() : '•••';
subtitle = '🌅 $m • ⚡ $d • 🌙 $e';
} else {
subtitle = 'Печати суток не распечатаны';
}
} else {
subtitle = rec.isRevealed
? '${singleWord.word.toUpperCase()} (#${singleWord.sessionNumber})'
: 'Печать не распечатана';
}
String statusText;
if (isDone) {
statusText = 'Выполнено ✓';
} else if (isTriptych) {
statusText = rec.areAllTriptychRevealed
? 'Открыты 3/3'
: (rec.isAnyRevealed ? 'В процессе' : 'Триптих');
} else {
statusText = rec.isRevealed ? 'В процессе' : 'Открыть';
}
String descText;
if (isTriptych) {
if (rec.areAllTriptychRevealed) {
descText = 'Утро: «${triptych.morning.asks}» | Вызов дня: «${triptych.day.step}»';
} else {
descText = 'Коснитесь, чтобы распечатать три слова дня: Настройка, Действие и Тень.';
}
} else {
descText = rec.isRevealed
? (singleWord.asks.isNotEmpty
? '«${singleWord.asks}»'
: (singleWord.oracle.isNotEmpty ? singleWord.oracle : singleWord.description))
: 'Коснитесь, чтобы узнать архетип, коан и практическое задание на сегодня.';
}
return InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => const WordOfTheDayScreen()),
).then((_) => setState(() {}));
},
borderRadius: BorderRadius.circular(20),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF2E2045), Color(0xFF14172B)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: isRevealed
? const Color(0xFFFFD54F)
: const Color(0xFFFFD54F).withValues(alpha: 0.5),
width: isRevealed ? 1.5 : 1.0,
),
boxShadow: [
BoxShadow(
color: const Color(0xFFFFD54F).withValues(alpha: 0.12),
blurRadius: 12,
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 38,
height: 38,
alignment: Alignment.center,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: const LinearGradient(
colors: [Color(0xFFFFD54F), Color(0xFFFF8F00)],
),
boxShadow: [
BoxShadow(
color: const Color(0xFFFFD54F).withValues(alpha: 0.3),
blurRadius: 8,
),
],
),
child: Text(
isTriptych
? '⏳'
: (rec.isRevealed && singleWord.emoji.isNotEmpty
? singleWord.emoji
: '☀️'),
style: const TextStyle(fontSize: 18),
),
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
titleTag,
style: const TextStyle(
color: Color(0xFFFFD54F),
fontSize: 11,
fontWeight: FontWeight.w900,
letterSpacing: 1.0,
),
),
if (streak > 0) ...[
const SizedBox(width: 6),
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: const Color(0xFF3B2314),
borderRadius: BorderRadius.circular(6),
border: Border.all(color: const Color(0xFFFFB74D).withValues(alpha: 0.4)),
),
child: Text(
'🔥 $streak дн.',
style: const TextStyle(
color: Color(0xFFFFB74D),
fontSize: 10,
fontWeight: FontWeight.bold,
),
),
),
],
],
),
const SizedBox(height: 2),
Text(
subtitle,
style: TextStyle(
color: isRevealed ? const Color(0xFFFFF9C4) : const Color(0xFF8C93B8),
fontSize: isTriptych ? 12.5 : 14,
fontWeight: FontWeight.bold,
),
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: isDone
? const Color(0xFF1B3D23)
: (isRevealed ? const Color(0xFF2A2D4A) : const Color(0xFF332A1C)),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isDone
? const Color(0xFF81C784)
: const Color(0xFFFFD54F).withValues(alpha: 0.4),
),
),
child: Text(
statusText,
style: TextStyle(
color: isDone
? const Color(0xFF81C784)
: const Color(0xFFFFD54F),
fontSize: 11,
fontWeight: FontWeight.bold,
),
),
),
],
),
const SizedBox(height: 10),
Text(
descText,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(color: Color(0xFF8C93B8), fontSize: 12, height: 1.35),
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
final session = _repo.currentSyncreticSession;
final readingsCount = session.readings.length;
return Scaffold(
backgroundColor: const Color(0xFF0C0E1A),
appBar: AppBar(
title: const Row(
children: [
GradientStar(size: 22),
SizedBox(width: 8),
Text('Предсказания', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
],
),
actions: [
IconButton(
tooltip: 'Начать новую тему сессии',
icon: const Icon(Icons.add_circle_outline_rounded, color: Color(0xFFFFD54F)),
onPressed: _showNewSessionDialog,
),
IconButton(
tooltip: 'Сравнительный Мета-Промпт',
icon: const Icon(Icons.psychology_alt_rounded, color: Color(0xFFFFD54F)),
onPressed: _openComparisonScreen,
),
],
),
body: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Word of the Day Hero Banner
_buildWordOfDayHeroBanner(),
const SizedBox(height: 14),
// Active Day Session Banner
InkWell(
onTap: _openComparisonScreen,
borderRadius: BorderRadius.circular(20),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF2A1F45), Color(0xFF151829)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: const Color(0xFFFFD54F).withValues(alpha: 0.5)),
boxShadow: [
BoxShadow(color: const Color(0xFFFFD54F).withValues(alpha: 0.1), blurRadius: 10),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const GradientStar(size: 18),
const SizedBox(width: 8),
const Expanded(
child: Text(
'АКТИВНАЯ СЕССИЯ ДНЯ',
style: TextStyle(
color: Color(0xFFFFD54F),
fontSize: 11,
fontWeight: FontWeight.w900,
letterSpacing: 1.0,
),
),
),
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(
'$readingsCount ${readingsCount == 1 ? "гадание" : (readingsCount < 5 ? "гадания" : "гаданий")}',
style: const TextStyle(color: Color(0xFFFFF9C4), fontSize: 11, fontWeight: FontWeight.bold),
),
),
],
),
const SizedBox(height: 8),
Text(
session.mainQuestion?.isNotEmpty == true
? '«${session.mainQuestion!}»'
: 'Синхронистический сосуд дня (все гадания аккумулируются здесь)',
style: const TextStyle(color: Color(0xFFECEFF1), fontSize: 13.5, height: 1.4),
),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
readingsCount > 0 ? 'Нажмите, чтобы сравнить системы' : 'Сделайте расклад в любой системе ниже',
style: const TextStyle(color: Color(0xFF80DEEA), fontSize: 11, fontWeight: FontWeight.bold),
),
const Icon(Icons.arrow_forward_ios_rounded, size: 12, color: Color(0xFFFFD54F)),
],
),
],
),
),
),
const SizedBox(height: 20),
// Section 1: Core 999 Systems
_buildSectionHeader('СИСТЕМЫ ОРАКУЛА 999', Icons.auto_awesome_rounded),
const SizedBox(height: 10),
Row(
children: [
Expanded(
child: _buildSystemCard(
title: 'Слово Дня',
instructionId: 'word_of_day',
subtitle: 'Календарь & Квест',
icon: '☀️',
desc: 'Ежедневный архетип, коан для ума, микродействие и дневник',
accentColor: const Color(0xFFFFD54F),
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const WordOfTheDayScreen())).then((_) => setState(() {})),
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: _buildSystemCard(
title: 'Оракул',
instructionId: 'cleromancy',
subtitle: 'Клеромантия d10',
icon: '🎲',
desc: '1..10 знаков по жребию костей или прямому вводу',
accentColor: const Color(0xFFFFD54F),
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const HomeSearchScreen())).then((_) => setState(() {})),
),
),
const SizedBox(width: 12),
Expanded(
child: _buildSystemCard(
title: 'Матрица 33×30',
instructionId: 'matrix',
subtitle: '990 Домов Судьбы',
icon: '🏛️',
desc: 'Полотно Домов, Архонты и цепочки диспозиций',
accentColor: const Color(0xFF80DEEA),
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const HousesFieldScreen())).then((_) => setState(() {})),
),
),
],
),
const SizedBox(height: 20),
// Section 2: Classical Divination Systems
_buildSectionHeader('КЛАССИЧЕСКИЕ ТРАДИЦИИ', Icons.history_edu_rounded),
const SizedBox(height: 10),
Row(
children: [
Expanded(
child: _buildSystemCard(
title: 'Руны Футарка',
instructionId: 'runes',
subtitle: '24 Руны (2×d12)',
icon: 'ᚱ',
desc: 'Жребий двумя d12 либо сетка рун (прямые/перев.)',
accentColor: const Color(0xFF81C784),
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const RunesDivinationScreen())).then((_) => setState(() {})),
),
),
const SizedBox(width: 12),
Expanded(
child: _buildSystemCard(
title: 'Кельтский Огам',
instructionId: 'ogham',
subtitle: '20 Фед Рощи (d20)',
icon: '᚛',
desc: 'Священные деревья друидов: жребий палочек и 4 Аикмы',
accentColor: const Color(0xFF66BB6A),
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const OghamDivinationScreen())).then((_) => setState(() {})),
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: _buildSystemCard(
title: 'Таро',
instructionId: 'tarot',
subtitle: '22 Старших Аркана',
icon: '🎴',
desc: 'Жребий колоды или выбор из 22 Арканов',
accentColor: const Color(0xFFBA68C8),
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const TarotDivinationScreen())).then((_) => setState(() {})),
),
),
const SizedBox(width: 12),
Expanded(
child: _buildSystemCard(
title: 'Ленорман',
instructionId: 'lenormand',
subtitle: '36 Домов (d6+d12)',
icon: '🪶',
desc: 'Бросок d6+d12 со смещением или ввод 1..36',
accentColor: const Color(0xFFFFB74D),
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const LenormandDivinationScreen())).then((_) => setState(() {})),
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: _buildSystemCard(
title: 'И Цзин',
instructionId: 'iching',
subtitle: '3 монеты × 6',
icon: '☯️',
desc: 'Книга Перемен: 6 бросков монет или ввод черт',
accentColor: const Color(0xFF4DD0E1),
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const IChingDivinationScreen())).then((_) => setState(() {})),
),
),
const SizedBox(width: 12),
Expanded(
child: _buildSystemCard(
title: 'Геомантия',
instructionId: 'geomancy',
subtitle: "16 Фигур ('Илм ар-рамл)",
icon: '⌛',
desc: 'Земной жребий песка, Свидетели и Щит Судьбы',
accentColor: const Color(0xFFFFB74D),
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const GeomancyDivinationScreen())).then((_) => setState(() {})),
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: _buildSystemCard(
title: 'Нумерология',
instructionId: 'numerology',
subtitle: 'Психоматрица 3×3',
icon: '🔢',
desc: '4 рабочих числа, психоматрица Пифагора и Число Судьбы',
accentColor: const Color(0xFFFFD54F),
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const NumerologyDivinationScreen())).then((_) => setState(() {})),
),
),
],
),
],
),
),
);
}
Widget _buildSectionHeader(String title, IconData icon) {
return Row(
children: [
Icon(icon, size: 16, color: const Color(0xFFFFD54F)),
const SizedBox(width: 8),
Text(
title,
style: const TextStyle(
color: Color(0xFFFFD54F),
fontSize: 11.5,
fontWeight: FontWeight.w900,
letterSpacing: 1.0,
),
),
],
);
}
Widget _buildSystemCard({
required String title,
required String subtitle,
required String icon,
required String desc,
required Color accentColor,
String? instructionId,
required VoidCallback onTap,
}) {
return InkWell(
onTap: () {
HapticFeedback.selectionClick();
onTap();
},
borderRadius: BorderRadius.circular(16),
child: Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xFF151829),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFF2A2D4A)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 36,
height: 36,
alignment: Alignment.center,
decoration: BoxDecoration(
color: const Color(0xFF232742),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: accentColor.withValues(alpha: 0.4)),
),
child: Text(icon, style: const TextStyle(fontSize: 18)),
),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 13)),
Text(subtitle, style: TextStyle(color: accentColor, fontSize: 10, fontWeight: FontWeight.bold)),
],
),
),
if (instructionId != null)
IconButton(
icon: const Icon(Icons.info_outline_rounded, size: 18, color: Color(0xFF8C93B8)),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
tooltip: 'Инструкция',
onPressed: () => DivinationInstructionModal.show(context, instructionId),
),
],
),
const SizedBox(height: 8),
Text(
desc,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(color: Color(0xFF8C93B8), fontSize: 11, height: 1.3),
),
],
),
),
);
}
}