Files
999/lib/screens/divination_hub_screen.dart

644 lines
26 KiB
Dart
Raw Permalink 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 '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: '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),
),
],
),
),
);
}
}