1862 lines
68 KiB
Dart
1862 lines
68 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:flutter/services.dart';
|
||
import '../models/syncretic_session_model.dart';
|
||
import '../models/history_entry_model.dart';
|
||
import '../models/word_model.dart';
|
||
import '../models/word_of_day_model.dart';
|
||
import '../services/ai_oracle_service.dart';
|
||
import '../services/day_numerology.dart';
|
||
import '../services/settings_repository.dart';
|
||
import '../services/word_of_the_day_service.dart';
|
||
import '../services/words_repository.dart';
|
||
import '../widgets/divination_instruction_modal.dart';
|
||
import '../widgets/gradient_star_badge.dart';
|
||
import 'symbol_detail_screen.dart';
|
||
|
||
class WordOfTheDayScreen extends StatefulWidget {
|
||
final DateTime? initialDate;
|
||
|
||
const WordOfTheDayScreen({super.key, this.initialDate});
|
||
|
||
@override
|
||
State<WordOfTheDayScreen> createState() => _WordOfTheDayScreenState();
|
||
}
|
||
|
||
class _WordOfTheDayScreenState extends State<WordOfTheDayScreen>
|
||
with SingleTickerProviderStateMixin {
|
||
final _service = WordOfTheDayService();
|
||
final _wordsRepo = WordsRepository();
|
||
final _aiService = AiOracleService();
|
||
final _settingsRepo = SettingsRepository();
|
||
|
||
late DateTime _selectedDate;
|
||
late TextEditingController _diaryController;
|
||
late TabController _tabController;
|
||
|
||
bool _isLoadingAi = false;
|
||
String? _aiAdvice;
|
||
bool _isDiarySaved = false;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_selectedDate = widget.initialDate ?? DateTime.now();
|
||
_tabController = TabController(length: 3, vsync: this);
|
||
final rec = _service.getRecord(_selectedDate);
|
||
_diaryController = TextEditingController(text: rec.diaryNote);
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_diaryController.dispose();
|
||
_tabController.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
void _changeDate(DateTime newDate) {
|
||
setState(() {
|
||
_selectedDate = newDate;
|
||
final rec = _service.getRecord(_selectedDate);
|
||
_diaryController.text = rec.diaryNote;
|
||
_aiAdvice = null;
|
||
_isDiarySaved = false;
|
||
});
|
||
}
|
||
|
||
void _previousDay() {
|
||
HapticFeedback.selectionClick();
|
||
_changeDate(_selectedDate.subtract(const Duration(days: 1)));
|
||
}
|
||
|
||
void _nextDay() {
|
||
HapticFeedback.selectionClick();
|
||
_changeDate(_selectedDate.add(const Duration(days: 1)));
|
||
}
|
||
|
||
void _jumpToToday() {
|
||
HapticFeedback.mediumImpact();
|
||
_changeDate(DateTime.now());
|
||
}
|
||
|
||
Future<void> _pickDate() async {
|
||
final picked = await showDatePicker(
|
||
context: context,
|
||
initialDate: _selectedDate,
|
||
firstDate: DateTime(2020),
|
||
lastDate: DateTime(2035),
|
||
builder: (context, child) {
|
||
return Theme(
|
||
data: Theme.of(context).copyWith(
|
||
colorScheme: const ColorScheme.dark(
|
||
primary: Color(0xFFFFD54F),
|
||
onPrimary: Color(0xFF151829),
|
||
surface: Color(0xFF151829),
|
||
onSurface: Color(0xFFE8EAF6),
|
||
),
|
||
),
|
||
child: child!,
|
||
);
|
||
},
|
||
);
|
||
if (picked != null) {
|
||
_changeDate(picked);
|
||
}
|
||
}
|
||
|
||
String _formatDateTitle(DateTime d) {
|
||
final now = DateTime.now();
|
||
final isToday = d.year == now.year && d.month == now.month && d.day == now.day;
|
||
final isYesterday = d.year == now.year &&
|
||
d.month == now.month &&
|
||
d.day == now.day - 1;
|
||
final isTomorrow = d.year == now.year &&
|
||
d.month == now.month &&
|
||
d.day == now.day + 1;
|
||
|
||
const months = [
|
||
'января', 'февраля', 'марта', 'апреля', 'мая', 'июня',
|
||
'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'
|
||
];
|
||
final dateStr = '${d.day} ${months[d.month - 1]} ${d.year}';
|
||
|
||
if (isToday) return 'СЕГОДНЯ • $dateStr';
|
||
if (isYesterday) return 'ВЧЕРА • $dateStr';
|
||
if (isTomorrow) return 'ЗАВТРА • $dateStr';
|
||
return dateStr;
|
||
}
|
||
|
||
DayCode _dayCode() => DayNumerology.ofDate(_selectedDate);
|
||
|
||
ReadingSignItem _dayCodeSign() {
|
||
final code = _dayCode();
|
||
final linked = code.word.isEmpty ? null : _wordsRepo.getWordByName(code.word);
|
||
return ReadingSignItem(
|
||
title: 'Код суток: ${code.value}',
|
||
subtitle: code.title,
|
||
symbol: '${code.value}',
|
||
lore: code.clause,
|
||
linkedWord: linked,
|
||
meta: {
|
||
'type': 'day_numerology',
|
||
'number': code.value,
|
||
'formula': code.formula,
|
||
},
|
||
);
|
||
}
|
||
|
||
void _openDayCodeWord() {
|
||
final linked = _wordsRepo.getWordByName(_dayCode().word);
|
||
if (linked == null) return;
|
||
HapticFeedback.selectionClick();
|
||
Navigator.push(
|
||
context,
|
||
MaterialPageRoute(builder: (_) => SymbolDetailScreen(word: linked)),
|
||
);
|
||
}
|
||
|
||
Widget _buildDayCodeCard() {
|
||
final code = _dayCode();
|
||
return Material(
|
||
color: const Color(0xFF151829),
|
||
borderRadius: BorderRadius.circular(16),
|
||
child: InkWell(
|
||
borderRadius: BorderRadius.circular(16),
|
||
onTap: _openDayCodeWord,
|
||
child: Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
||
decoration: BoxDecoration(
|
||
borderRadius: BorderRadius.circular(16),
|
||
border: Border.all(color: const Color(0xFFFFD54F).withValues(alpha: 0.35)),
|
||
),
|
||
child: Row(
|
||
children: [
|
||
Container(
|
||
width: 48,
|
||
height: 48,
|
||
alignment: Alignment.center,
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFF2A2412),
|
||
borderRadius: BorderRadius.circular(12),
|
||
),
|
||
child: Text(
|
||
'${code.value}',
|
||
style: const TextStyle(
|
||
color: Color(0xFFFFD54F),
|
||
fontSize: 20,
|
||
fontWeight: FontWeight.w900,
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(width: 12),
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
const Text(
|
||
'КОД СУТОК',
|
||
style: TextStyle(
|
||
color: Color(0xFFFFD54F),
|
||
fontSize: 10,
|
||
fontWeight: FontWeight.w800,
|
||
letterSpacing: 0.8,
|
||
),
|
||
),
|
||
const SizedBox(height: 2),
|
||
Text(
|
||
code.word.isEmpty ? code.title : '«${code.word}» · ${code.title}',
|
||
style: const TextStyle(color: Color(0xFFFFF9C4), fontSize: 13, fontWeight: FontWeight.w700),
|
||
),
|
||
const SizedBox(height: 2),
|
||
Text(
|
||
'день ${code.reducedDay} · месяц ${code.reducedMonth} · год ${code.reducedYear}',
|
||
style: const TextStyle(color: Color(0xFF8C93B8), fontSize: 11),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const Icon(Icons.chevron_right_rounded, color: Color(0xFFFFD54F)),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Future<void> _revealWord({String phase = 'main'}) async {
|
||
HapticFeedback.heavyImpact();
|
||
await _service.revealWord(_selectedDate, phase: phase);
|
||
setState(() {});
|
||
}
|
||
|
||
Future<void> _toggleStep(bool completed, {bool isDayStep = false}) async {
|
||
HapticFeedback.mediumImpact();
|
||
await _service.toggleStepCompleted(_selectedDate, completed, isDayStep: isDayStep);
|
||
setState(() {});
|
||
}
|
||
|
||
Future<void> _saveDiary() async {
|
||
HapticFeedback.mediumImpact();
|
||
await _service.saveDiaryNote(_selectedDate, _diaryController.text);
|
||
setState(() {
|
||
_isDiarySaved = true;
|
||
});
|
||
if (mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: const Row(
|
||
children: [
|
||
GradientStar(size: 16),
|
||
SizedBox(width: 8),
|
||
Text('Синхронистическая заметка сохранена в летопись'),
|
||
],
|
||
),
|
||
duration: const Duration(seconds: 2),
|
||
behavior: SnackBarBehavior.floating,
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
Future<void> _toggleMode(bool isTriptych) async {
|
||
HapticFeedback.selectionClick();
|
||
await _service.setRecordTriptychMode(_selectedDate, isTriptych);
|
||
await _service.setGlobalTriptychMode(isTriptych);
|
||
setState(() {});
|
||
}
|
||
|
||
Future<void> _weaveIntoSyncreticSessionSingle(WordItem word) async {
|
||
HapticFeedback.mediumImpact();
|
||
final reading = SyncreticReading(
|
||
id: 'sod_${DateTime.now().millisecondsSinceEpoch}',
|
||
systemId: 'word_of_day',
|
||
systemName: 'Слово Дня',
|
||
systemIcon: '☀️',
|
||
timestamp: DateTime.now(),
|
||
layoutTitle: 'Архетип Дня • ${_formatDateTitle(_selectedDate)}',
|
||
inputMethod: 'calendar',
|
||
items: [
|
||
ReadingSignItem(
|
||
title: word.word.toUpperCase(),
|
||
subtitle: word.category.isNotEmpty ? word.category : 'Слово Дня',
|
||
symbol: word.emoji.isNotEmpty ? word.emoji : '☀️',
|
||
lore: word.oracle.isNotEmpty ? word.oracle : word.cleanDescription,
|
||
linkedWord: word,
|
||
meta: {
|
||
'dateKey': WordOfTheDayService.formatDateKey(_selectedDate),
|
||
'core': word.core,
|
||
'asks': word.asks,
|
||
'step': word.step,
|
||
},
|
||
),
|
||
_dayCodeSign(),
|
||
],
|
||
);
|
||
|
||
await _wordsRepo.addReadingToSyncreticSession(reading);
|
||
|
||
if (mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: Row(
|
||
children: [
|
||
const GradientStar(size: 18),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
child: Text('«${word.word.toUpperCase()}» вплетено в Сессию Дня!'),
|
||
),
|
||
],
|
||
),
|
||
duration: const Duration(seconds: 2),
|
||
behavior: SnackBarBehavior.floating,
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
Future<void> _weaveTriptychIntoSyncreticSession(
|
||
WordItem morning,
|
||
WordItem day,
|
||
WordItem evening,
|
||
) async {
|
||
HapticFeedback.mediumImpact();
|
||
final reading = SyncreticReading(
|
||
id: 'trip_${DateTime.now().millisecondsSinceEpoch}',
|
||
systemId: 'word_of_day',
|
||
systemName: 'Триптих Дня',
|
||
systemIcon: '⏳',
|
||
timestamp: DateTime.now(),
|
||
layoutTitle: 'Триптих Суток (Утро • День • Вечер) • ${_formatDateTitle(_selectedDate)}',
|
||
inputMethod: 'calendar_triptych',
|
||
items: [
|
||
ReadingSignItem(
|
||
title: '🌅 УТРО: ${morning.word.toUpperCase()}',
|
||
subtitle: morning.category,
|
||
symbol: morning.emoji.isNotEmpty ? morning.emoji : '🌅',
|
||
lore: morning.core.isNotEmpty ? morning.core : morning.oracle,
|
||
linkedWord: morning,
|
||
),
|
||
ReadingSignItem(
|
||
title: '⚡ ДЕНЬ: ${day.word.toUpperCase()}',
|
||
subtitle: day.category,
|
||
symbol: day.emoji.isNotEmpty ? day.emoji : '⚡',
|
||
lore: day.step.isNotEmpty ? day.step : day.oracle,
|
||
linkedWord: day,
|
||
),
|
||
ReadingSignItem(
|
||
title: '🌙 ВЕЧЕР: ${evening.word.toUpperCase()}',
|
||
subtitle: evening.category,
|
||
symbol: evening.emoji.isNotEmpty ? evening.emoji : '🌙',
|
||
lore: evening.shadow.isNotEmpty ? evening.shadow : evening.oracle,
|
||
linkedWord: evening,
|
||
),
|
||
],
|
||
);
|
||
|
||
await _wordsRepo.addReadingToSyncreticSession(reading);
|
||
|
||
if (mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: const Row(
|
||
children: [
|
||
GradientStar(size: 18),
|
||
SizedBox(width: 8),
|
||
Expanded(
|
||
child: Text('Триптих дня (Утро, День, Вечер) вплетён в Сессию!'),
|
||
),
|
||
],
|
||
),
|
||
duration: const Duration(seconds: 2),
|
||
behavior: SnackBarBehavior.floating,
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
Future<void> _consultOracleSingle(WordItem word) async {
|
||
setState(() => _isLoadingAi = true);
|
||
HapticFeedback.mediumImpact();
|
||
try {
|
||
final promptQuestion =
|
||
'Слово Дня: «${word.word.toUpperCase()}». ${_dayCode().clause} Напутствие на день: как прожить этот архетип осознанно, не упав в тень?';
|
||
final advice = await _aiService.interpret(
|
||
word: word,
|
||
question: promptQuestion,
|
||
positionTitle: 'Камертон дня',
|
||
);
|
||
if (mounted) {
|
||
setState(() {
|
||
_aiAdvice = advice;
|
||
_isLoadingAi = false;
|
||
});
|
||
}
|
||
} catch (e) {
|
||
if (mounted) {
|
||
setState(() => _isLoadingAi = false);
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(content: Text('Ошибка связи с Оракулом: $e')),
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
Future<void> _consultOracleTriptych(
|
||
WordItem morning,
|
||
WordItem day,
|
||
WordItem evening,
|
||
) async {
|
||
setState(() => _isLoadingAi = true);
|
||
HapticFeedback.mediumImpact();
|
||
try {
|
||
final promptQuestion =
|
||
'Триптих Дня: 🌅 Утро — «${morning.word.toUpperCase()}» (Архетип настройки); '
|
||
'⚡ День — «${day.word.toUpperCase()}» (Действие и вызов); '
|
||
'🌙 Вечер — «${evening.word.toUpperCase()}» (Тень и интеграция). '
|
||
'${_dayCode().clause} Дай синтез сюжета дня: как утреннее намерение воплотить в дневном поступке и осмыслить вечером?';
|
||
|
||
final advice = await _aiService.interpretSession(
|
||
words: [
|
||
SessionWord.fromWordItem(morning, positionTitle: 'Утро (Камертон)'),
|
||
SessionWord.fromWordItem(day, positionTitle: 'День (Действие)'),
|
||
SessionWord.fromWordItem(evening, positionTitle: 'Вечер (Интеграция)'),
|
||
],
|
||
question: promptQuestion,
|
||
);
|
||
|
||
if (mounted) {
|
||
setState(() {
|
||
_aiAdvice = advice;
|
||
_isLoadingAi = false;
|
||
});
|
||
}
|
||
} catch (e) {
|
||
if (mounted) {
|
||
setState(() => _isLoadingAi = false);
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(content: Text('Ошибка связи с Оракулом: $e')),
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
void _shareSingle(WordItem word, WordOfDayRecord rec) {
|
||
HapticFeedback.lightImpact();
|
||
final buf = StringBuffer();
|
||
buf.writeln('☀️ СЛОВО ДНЯ • ${_formatDateTitle(_selectedDate)}');
|
||
buf.writeln('🔢 Код суток: ${_dayCode().formula} · ${_dayCode().word}');
|
||
buf.writeln('Символ: ${word.emoji} ${word.word.toUpperCase()} (#${word.sessionNumber})');
|
||
if (word.category.isNotEmpty) buf.writeln('Категория: ${word.category}');
|
||
if (word.core.isNotEmpty) buf.writeln('\n🏛️ Архетип:\n${word.core}');
|
||
if (word.oracle.isNotEmpty) buf.writeln('\n🔮 Оракул:\n${word.oracle}');
|
||
if (word.asks.isNotEmpty) buf.writeln('\n🧘 Коан для ума:\n«${word.asks}»');
|
||
if (word.step.isNotEmpty) {
|
||
buf.writeln('\n⚡ Практикум дня:\n${word.step}');
|
||
buf.writeln('Статус: ${rec.isStepCompleted ? "Выполнено ✓" : "В процессе"}');
|
||
}
|
||
if (word.shadow.isNotEmpty) buf.writeln('\n🌑 Теневая ловушка:\n${word.shadow}');
|
||
if (rec.diaryNote.isNotEmpty) {
|
||
buf.writeln('\n🪶 Дневник синхроний:\n${rec.diaryNote}');
|
||
}
|
||
|
||
Clipboard.setData(ClipboardData(text: buf.toString()));
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: const Row(
|
||
children: [
|
||
GradientStar(size: 16),
|
||
SizedBox(width: 8),
|
||
Text('Карточка Слова Дня скопирована в буфер обмена'),
|
||
],
|
||
),
|
||
duration: const Duration(seconds: 2),
|
||
behavior: SnackBarBehavior.floating,
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||
),
|
||
);
|
||
}
|
||
|
||
void _shareTriptych(
|
||
WordItem morning,
|
||
WordItem day,
|
||
WordItem evening,
|
||
WordOfDayRecord rec,
|
||
) {
|
||
HapticFeedback.lightImpact();
|
||
final buf = StringBuffer();
|
||
buf.writeln('⏳ ТРИПТИХ ДНЯ • ${_formatDateTitle(_selectedDate)}');
|
||
buf.writeln('🔢 Код суток: ${_dayCode().formula} · ${_dayCode().word}');
|
||
buf.writeln('\n🌅 УТРО — ${morning.emoji} ${morning.word.toUpperCase()} (#${morning.sessionNumber})');
|
||
if (morning.core.isNotEmpty) buf.writeln(' Архетип: ${morning.core}');
|
||
if (morning.asks.isNotEmpty) buf.writeln(' Коан: «${morning.asks}»');
|
||
|
||
buf.writeln('\n⚡ ДЕНЬ — ${day.emoji} ${day.word.toUpperCase()} (#${day.sessionNumber})');
|
||
if (day.step.isNotEmpty) buf.writeln(' Вызов дня: ${day.step}');
|
||
buf.writeln(' Статус: ${rec.isDayStepCompleted ? "Выполнено ✓" : "В процессе"}');
|
||
|
||
buf.writeln('\n🌙 ВЕЧЕР — ${evening.emoji} ${evening.word.toUpperCase()} (#${evening.sessionNumber})');
|
||
if (evening.shadow.isNotEmpty) buf.writeln(' Тень: ${evening.shadow}');
|
||
if (rec.diaryNote.isNotEmpty) {
|
||
buf.writeln(' 🪶 Дневник синхроний: ${rec.diaryNote}');
|
||
}
|
||
|
||
Clipboard.setData(ClipboardData(text: buf.toString()));
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: const Row(
|
||
children: [
|
||
GradientStar(size: 16),
|
||
SizedBox(width: 8),
|
||
Text('Триптих дня скопирован в буфер обмена'),
|
||
],
|
||
),
|
||
duration: const Duration(seconds: 2),
|
||
behavior: SnackBarBehavior.floating,
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||
),
|
||
);
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final record = _service.getRecord(_selectedDate);
|
||
final isTriptych = record.isTriptychMode;
|
||
final streak = _service.calculateStreak();
|
||
final isToday = WordOfTheDayService.formatDateKey(_selectedDate) ==
|
||
WordOfTheDayService.formatDateKey(DateTime.now());
|
||
|
||
final singleWord = _service.getWordForDate(_selectedDate, phase: 'main');
|
||
final triptych = _service.getTriptychWordsForDate(_selectedDate);
|
||
|
||
return Scaffold(
|
||
backgroundColor: const Color(0xFF0C0E1A),
|
||
appBar: AppBar(
|
||
title: Row(
|
||
children: [
|
||
const GradientStar(size: 22),
|
||
const SizedBox(width: 8),
|
||
Text(
|
||
isTriptych ? 'Триптих Дня' : 'Слово Дня',
|
||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
|
||
),
|
||
],
|
||
),
|
||
actions: [
|
||
IconButton(
|
||
tooltip: 'Выбрать дату',
|
||
icon: const Icon(Icons.calendar_month_rounded, color: Color(0xFFFFD54F)),
|
||
onPressed: _pickDate,
|
||
),
|
||
IconButton(
|
||
tooltip: 'Поделиться',
|
||
icon: const Icon(Icons.share_rounded, color: Color(0xFFFFD54F)),
|
||
onPressed: () {
|
||
if (isTriptych) {
|
||
_shareTriptych(triptych.morning, triptych.day, triptych.evening, record);
|
||
} else {
|
||
_shareSingle(singleWord, record);
|
||
}
|
||
},
|
||
),
|
||
IconButton(
|
||
tooltip: 'Инструкция',
|
||
icon: const Icon(Icons.info_outline_rounded, color: Color(0xFF8C93B8)),
|
||
onPressed: () => DivinationInstructionModal.show(context, 'word_of_day'),
|
||
),
|
||
],
|
||
),
|
||
body: SingleChildScrollView(
|
||
padding: const EdgeInsets.fromLTRB(16, 4, 16, 32),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||
children: [
|
||
// Date Navigation Header
|
||
_buildDateBar(streak, isToday),
|
||
const SizedBox(height: 12),
|
||
_buildDayCodeCard(),
|
||
const SizedBox(height: 12),
|
||
|
||
// Mode Selector: 1 Word vs 3 Words Triptych
|
||
_buildModeSelector(isTriptych),
|
||
const SizedBox(height: 16),
|
||
|
||
// Main Content: Single Word Mode OR Triptych Mode
|
||
if (!isTriptych) ...[
|
||
_buildSingleWordBody(singleWord, record),
|
||
] else ...[
|
||
_buildTriptychBody(triptych.morning, triptych.day, triptych.evening, record),
|
||
],
|
||
|
||
if (_isLoadingAi) ...[
|
||
const SizedBox(height: 16),
|
||
const Center(
|
||
child: Padding(
|
||
padding: EdgeInsets.all(16.0),
|
||
child: CircularProgressIndicator(color: Color(0xFFFFD54F)),
|
||
),
|
||
),
|
||
] else if (_aiAdvice != null) ...[
|
||
const SizedBox(height: 16),
|
||
_buildAiAdviceCard(),
|
||
],
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildDateBar(int streak, bool isToday) {
|
||
return Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFF151829),
|
||
borderRadius: BorderRadius.circular(16),
|
||
border: Border.all(color: const Color(0xFF232742)),
|
||
),
|
||
child: Row(
|
||
children: [
|
||
IconButton(
|
||
icon: const Icon(Icons.chevron_left_rounded, color: Color(0xFFFFD54F)),
|
||
onPressed: _previousDay,
|
||
tooltip: 'Предыдущий день',
|
||
),
|
||
Expanded(
|
||
child: InkWell(
|
||
onTap: _pickDate,
|
||
borderRadius: BorderRadius.circular(8),
|
||
child: Column(
|
||
children: [
|
||
Text(
|
||
_formatDateTitle(_selectedDate),
|
||
textAlign: TextAlign.center,
|
||
style: const TextStyle(
|
||
color: Color(0xFFFFF9C4),
|
||
fontSize: 13,
|
||
fontWeight: FontWeight.w900,
|
||
letterSpacing: 0.5,
|
||
),
|
||
),
|
||
if (!isToday)
|
||
GestureDetector(
|
||
onTap: _jumpToToday,
|
||
child: const Padding(
|
||
padding: EdgeInsets.only(top: 2),
|
||
child: Text(
|
||
'Вернуться к сегодня',
|
||
style: TextStyle(
|
||
color: Color(0xFF80DEEA),
|
||
fontSize: 11,
|
||
decoration: TextDecoration.underline,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
IconButton(
|
||
icon: const Icon(Icons.chevron_right_rounded, color: Color(0xFFFFD54F)),
|
||
onPressed: _nextDay,
|
||
tooltip: 'Следующий день',
|
||
),
|
||
if (streak > 0)
|
||
Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFF332014),
|
||
borderRadius: BorderRadius.circular(12),
|
||
border: Border.all(color: const Color(0xFFFFB74D).withValues(alpha: 0.5)),
|
||
),
|
||
child: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
const Text('🔥', style: TextStyle(fontSize: 13)),
|
||
const SizedBox(width: 4),
|
||
Text(
|
||
'$streak',
|
||
style: const TextStyle(
|
||
color: Color(0xFFFFB74D),
|
||
fontSize: 11.5,
|
||
fontWeight: FontWeight.w900,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildModeSelector(bool isTriptych) {
|
||
return Container(
|
||
padding: const EdgeInsets.all(4),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFF151829),
|
||
borderRadius: BorderRadius.circular(14),
|
||
border: Border.all(color: const Color(0xFF2A2D4A)),
|
||
),
|
||
child: Row(
|
||
children: [
|
||
Expanded(
|
||
child: InkWell(
|
||
onTap: () => _toggleMode(false),
|
||
borderRadius: BorderRadius.circular(10),
|
||
child: Container(
|
||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||
decoration: BoxDecoration(
|
||
color: !isTriptych ? const Color(0xFF2C2248) : Colors.transparent,
|
||
borderRadius: BorderRadius.circular(10),
|
||
border: !isTriptych
|
||
? Border.all(color: const Color(0xFFFFD54F).withValues(alpha: 0.6))
|
||
: null,
|
||
),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Text('☀️', style: TextStyle(fontSize: !isTriptych ? 14 : 12)),
|
||
const SizedBox(width: 6),
|
||
Text(
|
||
'Единый Камертон (1)',
|
||
style: TextStyle(
|
||
color: !isTriptych ? const Color(0xFFFFF9C4) : const Color(0xFF8C93B8),
|
||
fontWeight: FontWeight.bold,
|
||
fontSize: 12,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(width: 4),
|
||
Expanded(
|
||
child: InkWell(
|
||
onTap: () => _toggleMode(true),
|
||
borderRadius: BorderRadius.circular(10),
|
||
child: Container(
|
||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||
decoration: BoxDecoration(
|
||
color: isTriptych ? const Color(0xFF2C2248) : Colors.transparent,
|
||
borderRadius: BorderRadius.circular(10),
|
||
border: isTriptych
|
||
? Border.all(color: const Color(0xFFFFD54F).withValues(alpha: 0.6))
|
||
: null,
|
||
),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Text('⏳', style: TextStyle(fontSize: isTriptych ? 14 : 12)),
|
||
const SizedBox(width: 6),
|
||
Text(
|
||
'Триптих Суток (3)',
|
||
style: TextStyle(
|
||
color: isTriptych ? const Color(0xFFFFF9C4) : const Color(0xFF8C93B8),
|
||
fontWeight: FontWeight.bold,
|
||
fontSize: 12,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
// ==================== 1-WORD MODE ====================
|
||
|
||
Widget _buildSingleWordBody(WordItem word, WordOfDayRecord record) {
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||
children: [
|
||
if (!record.isRevealed)
|
||
_buildUnrevealedSeal(
|
||
title: 'ПЕЧАТЬ ДНЯ',
|
||
desc:
|
||
'Время сегодня звучит уникальным архетипом. Слово дня задаёт главный камертон событий, мыслей и внутренних озарений.',
|
||
buttonText: 'РАСПЕЧАТАТЬ СЛОВО ДНЯ',
|
||
onTap: () => _revealWord(phase: 'main'),
|
||
)
|
||
else
|
||
_buildRevealedTalisman(word),
|
||
|
||
if (record.isRevealed) ...[
|
||
const SizedBox(height: 20),
|
||
Container(
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFF151829),
|
||
borderRadius: BorderRadius.circular(14),
|
||
border: Border.all(color: const Color(0xFF2A2D4A)),
|
||
),
|
||
child: TabBar(
|
||
controller: _tabController,
|
||
indicatorColor: const Color(0xFFFFD54F),
|
||
indicatorSize: TabBarIndicatorSize.tab,
|
||
dividerColor: Colors.transparent,
|
||
labelColor: const Color(0xFFFFD54F),
|
||
unselectedLabelColor: const Color(0xFF8C93B8),
|
||
labelStyle: const TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
|
||
tabs: const [
|
||
Tab(icon: Icon(Icons.wb_twilight_rounded, size: 18), text: '1. Настройка'),
|
||
Tab(icon: Icon(Icons.flash_on_rounded, size: 18), text: '2. Практикум'),
|
||
Tab(icon: Icon(Icons.nights_stay_rounded, size: 18), text: '3. Синхронии'),
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(height: 14),
|
||
AnimatedBuilder(
|
||
animation: _tabController,
|
||
builder: (context, _) {
|
||
switch (_tabController.index) {
|
||
case 0:
|
||
return _buildMorningFocusTab(word);
|
||
case 1:
|
||
return _buildDailyPracticeTab(word, record);
|
||
case 2:
|
||
return _buildEveningDiaryTab(record);
|
||
default:
|
||
return const SizedBox.shrink();
|
||
}
|
||
},
|
||
),
|
||
const SizedBox(height: 20),
|
||
_buildActionRowSingle(word),
|
||
],
|
||
],
|
||
);
|
||
}
|
||
|
||
// ==================== 3-WORDS TRIPTYCH MODE ====================
|
||
|
||
Widget _buildTriptychBody(
|
||
WordItem morning,
|
||
WordItem day,
|
||
WordItem evening,
|
||
WordOfDayRecord record,
|
||
) {
|
||
final areAllRevealed = record.areAllTriptychRevealed;
|
||
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||
children: [
|
||
// Triptych Header Banner
|
||
Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFF1E1733),
|
||
borderRadius: BorderRadius.circular(14),
|
||
border: Border.all(color: const Color(0xFFFFD54F).withValues(alpha: 0.3)),
|
||
),
|
||
child: Row(
|
||
children: [
|
||
const Icon(Icons.auto_awesome_motion_rounded, size: 18, color: Color(0xFFFFD54F)),
|
||
const SizedBox(width: 8),
|
||
const Expanded(
|
||
child: Text(
|
||
'Сюжет суток: Утренний камертон ➔ Дневное действие ➔ Вечерняя интеграция',
|
||
style: TextStyle(color: Color(0xFFFFF9C4), fontSize: 11.5, height: 1.3),
|
||
),
|
||
),
|
||
if (!areAllRevealed)
|
||
TextButton(
|
||
style: TextButton.styleFrom(
|
||
visualDensity: VisualDensity.compact,
|
||
foregroundColor: const Color(0xFF80DEEA),
|
||
),
|
||
onPressed: () => _revealWord(phase: 'all'),
|
||
child: const Text('Открыть все', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 11)),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(height: 14),
|
||
|
||
// Phase 1: Morning
|
||
_buildPhaseCard(
|
||
phaseTitle: '1. УТРО • НАСТРОЙКА И АРХЕТИП',
|
||
phaseIcon: '🌅',
|
||
accentColor: const Color(0xFFFFD54F),
|
||
word: morning,
|
||
isRevealed: record.isMorningRevealed,
|
||
onReveal: () => _revealWord(phase: 'morning'),
|
||
content: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
morning.core.isNotEmpty ? morning.core : morning.description,
|
||
style: const TextStyle(color: Color(0xFFECEFF1), fontSize: 13, height: 1.4),
|
||
),
|
||
if (morning.asks.isNotEmpty) ...[
|
||
const SizedBox(height: 8),
|
||
Container(
|
||
padding: const EdgeInsets.all(10),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFF261D3A),
|
||
borderRadius: BorderRadius.circular(10),
|
||
border: Border.all(color: const Color(0xFFBA68C8).withValues(alpha: 0.4)),
|
||
),
|
||
child: Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
const Icon(Icons.self_improvement_rounded, size: 16, color: Color(0xFFBA68C8)),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
child: Text(
|
||
'Коан утра: «${morning.asks}»',
|
||
style: const TextStyle(
|
||
color: Color(0xFFFFF9C4),
|
||
fontSize: 12,
|
||
fontStyle: FontStyle.italic,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(height: 14),
|
||
|
||
// Phase 2: Day
|
||
_buildPhaseCard(
|
||
phaseTitle: '2. ДЕНЬ • ВЫЗОВ И ДЕЙСТВИЕ',
|
||
phaseIcon: '⚡',
|
||
accentColor: const Color(0xFF81C784),
|
||
word: day,
|
||
isRevealed: record.isDayRevealed,
|
||
onReveal: () => _revealWord(phase: 'day'),
|
||
content: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
day.step.isNotEmpty
|
||
? day.step
|
||
: 'Воплотите импульс слова в конкретном материальном действии или осознанном поступке.',
|
||
style: const TextStyle(color: Color(0xFFECEFF1), fontSize: 13, height: 1.4),
|
||
),
|
||
const SizedBox(height: 12),
|
||
SizedBox(
|
||
width: double.infinity,
|
||
child: ElevatedButton.icon(
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: record.isDayStepCompleted
|
||
? const Color(0xFF2E7D32)
|
||
: const Color(0xFF232A3E),
|
||
foregroundColor: Colors.white,
|
||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||
),
|
||
icon: Icon(
|
||
record.isDayStepCompleted
|
||
? Icons.check_circle_rounded
|
||
: Icons.radio_button_unchecked_rounded,
|
||
size: 18,
|
||
color: record.isDayStepCompleted
|
||
? const Color(0xFFFFF9C4)
|
||
: const Color(0xFF81C784),
|
||
),
|
||
label: Text(
|
||
record.isDayStepCompleted ? 'Дневной шаг выполнен ✓' : 'Отметить дневной шаг как выполненный',
|
||
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.bold),
|
||
),
|
||
onPressed: () => _toggleStep(!record.isDayStepCompleted, isDayStep: true),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(height: 14),
|
||
|
||
// Phase 3: Evening
|
||
_buildPhaseCard(
|
||
phaseTitle: '3. ВЕЧЕР • ТЕНЬ И ИНТЕГРАЦИЯ',
|
||
phaseIcon: '🌙',
|
||
accentColor: const Color(0xFF80DEEA),
|
||
word: evening,
|
||
isRevealed: record.isEveningRevealed,
|
||
onReveal: () => _revealWord(phase: 'evening'),
|
||
content: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
if (evening.shadow.isNotEmpty) ...[
|
||
Container(
|
||
padding: const EdgeInsets.all(10),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFF26191D),
|
||
borderRadius: BorderRadius.circular(10),
|
||
border: Border.all(color: const Color(0xFFFF7043).withValues(alpha: 0.4)),
|
||
),
|
||
child: Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
const Icon(Icons.warning_amber_rounded, size: 16, color: Color(0xFFFF7043)),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
child: Text(
|
||
'Теневой капкан вечера: ${evening.shadow}',
|
||
style: const TextStyle(color: Color(0xFFFFCCBC), fontSize: 12),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(height: 12),
|
||
],
|
||
_buildEveningDiaryTab(record),
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(height: 20),
|
||
|
||
// Action Buttons for Triptych
|
||
_buildActionRowTriptych(morning, day, evening),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget _buildPhaseCard({
|
||
required String phaseTitle,
|
||
required String phaseIcon,
|
||
required Color accentColor,
|
||
required WordItem word,
|
||
required bool isRevealed,
|
||
required VoidCallback onReveal,
|
||
required Widget content,
|
||
}) {
|
||
return Container(
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFF151829),
|
||
borderRadius: BorderRadius.circular(18),
|
||
border: Border.all(
|
||
color: isRevealed
|
||
? accentColor.withValues(alpha: 0.6)
|
||
: const Color(0xFF2A2D4A),
|
||
width: isRevealed ? 1.5 : 1.0,
|
||
),
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
// Header Bar
|
||
Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||
decoration: BoxDecoration(
|
||
color: accentColor.withValues(alpha: 0.08),
|
||
borderRadius: const BorderRadius.vertical(top: Radius.circular(17)),
|
||
),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Row(
|
||
children: [
|
||
Text(phaseIcon, style: const TextStyle(fontSize: 16)),
|
||
const SizedBox(width: 8),
|
||
Text(
|
||
phaseTitle,
|
||
style: TextStyle(
|
||
color: accentColor,
|
||
fontSize: 11,
|
||
fontWeight: FontWeight.w900,
|
||
letterSpacing: 0.8,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
if (isRevealed)
|
||
TextButton.icon(
|
||
style: TextButton.styleFrom(
|
||
visualDensity: VisualDensity.compact,
|
||
foregroundColor: const Color(0xFF80DEEA),
|
||
padding: EdgeInsets.zero,
|
||
),
|
||
icon: const Icon(Icons.arrow_forward, size: 14),
|
||
label: const Text('Корень', style: TextStyle(fontSize: 11)),
|
||
onPressed: () {
|
||
Navigator.push(
|
||
context,
|
||
MaterialPageRoute(builder: (_) => SymbolDetailScreen(word: word)),
|
||
);
|
||
},
|
||
),
|
||
],
|
||
),
|
||
),
|
||
|
||
Padding(
|
||
padding: const EdgeInsets.all(14),
|
||
child: !isRevealed
|
||
? Center(
|
||
child: Padding(
|
||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||
child: OutlinedButton.icon(
|
||
style: OutlinedButton.styleFrom(
|
||
foregroundColor: accentColor,
|
||
side: BorderSide(color: accentColor.withValues(alpha: 0.6)),
|
||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||
),
|
||
icon: const Icon(Icons.lock_open_rounded, size: 16),
|
||
label: Text('Распечатать ($phaseTitle)'.split('•').first.trim(),
|
||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 12)),
|
||
onPressed: onReveal,
|
||
),
|
||
),
|
||
)
|
||
: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Row(
|
||
children: [
|
||
Text(
|
||
word.emoji.isNotEmpty ? word.emoji : '✨',
|
||
style: const TextStyle(fontSize: 26),
|
||
),
|
||
const SizedBox(width: 10),
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
word.word.toUpperCase(),
|
||
style: const TextStyle(
|
||
color: Color(0xFFFFF9C4),
|
||
fontSize: 18,
|
||
fontWeight: FontWeight.w900,
|
||
letterSpacing: 1.0,
|
||
),
|
||
),
|
||
Text(
|
||
'#${word.formattedSessionNumber} • ${word.category} • ${word.localizedOperator}',
|
||
style: const TextStyle(color: Color(0xFF8C93B8), fontSize: 11),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 12),
|
||
content,
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
// ==================== SHARED WIDGETS ====================
|
||
|
||
Widget _buildUnrevealedSeal({
|
||
required String title,
|
||
required String desc,
|
||
required String buttonText,
|
||
required VoidCallback onTap,
|
||
}) {
|
||
return Container(
|
||
padding: const EdgeInsets.symmetric(vertical: 36, horizontal: 20),
|
||
decoration: BoxDecoration(
|
||
gradient: const RadialGradient(
|
||
center: Alignment(0, -0.2),
|
||
radius: 1.2,
|
||
colors: [
|
||
Color(0xFF2E2452),
|
||
Color(0xFF14172B),
|
||
Color(0xFF0C0E1A),
|
||
],
|
||
),
|
||
borderRadius: BorderRadius.circular(24),
|
||
border: Border.all(color: const Color(0xFFFFD54F).withValues(alpha: 0.5), width: 1.5),
|
||
boxShadow: [
|
||
BoxShadow(
|
||
color: const Color(0xFFFFD54F).withValues(alpha: 0.15),
|
||
blurRadius: 20,
|
||
spreadRadius: 2,
|
||
),
|
||
],
|
||
),
|
||
child: Column(
|
||
children: [
|
||
Container(
|
||
width: 80,
|
||
height: 80,
|
||
decoration: BoxDecoration(
|
||
shape: BoxShape.circle,
|
||
gradient: const LinearGradient(
|
||
colors: [Color(0xFFFFD54F), Color(0xFFFF8F00)],
|
||
begin: Alignment.topLeft,
|
||
end: Alignment.bottomRight,
|
||
),
|
||
boxShadow: [
|
||
BoxShadow(
|
||
color: const Color(0xFFFFD54F).withValues(alpha: 0.4),
|
||
blurRadius: 16,
|
||
spreadRadius: 2,
|
||
),
|
||
],
|
||
),
|
||
child: const Icon(
|
||
Icons.auto_awesome_rounded,
|
||
color: Color(0xFF0C0E1A),
|
||
size: 40,
|
||
),
|
||
),
|
||
const SizedBox(height: 18),
|
||
Text(
|
||
title,
|
||
style: const TextStyle(
|
||
color: Color(0xFFFFF9C4),
|
||
fontSize: 18,
|
||
fontWeight: FontWeight.w900,
|
||
letterSpacing: 2.0,
|
||
),
|
||
),
|
||
const SizedBox(height: 8),
|
||
Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||
child: Text(
|
||
desc,
|
||
textAlign: TextAlign.center,
|
||
style: const TextStyle(color: Color(0xFF8C93B8), fontSize: 13, height: 1.4),
|
||
),
|
||
),
|
||
const SizedBox(height: 22),
|
||
ElevatedButton.icon(
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: const Color(0xFFFFD54F),
|
||
foregroundColor: const Color(0xFF0C0E1A),
|
||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 13),
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||
elevation: 6,
|
||
),
|
||
icon: const Icon(Icons.lock_open_rounded, size: 20),
|
||
label: Text(
|
||
buttonText,
|
||
style: const TextStyle(fontWeight: FontWeight.w900, letterSpacing: 0.8, fontSize: 13),
|
||
),
|
||
onPressed: onTap,
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildRevealedTalisman(WordItem word) {
|
||
return Container(
|
||
padding: const EdgeInsets.all(20),
|
||
decoration: BoxDecoration(
|
||
gradient: const LinearGradient(
|
||
colors: [Color(0xFF241B42), Color(0xFF14172B)],
|
||
begin: Alignment.topLeft,
|
||
end: Alignment.bottomRight,
|
||
),
|
||
borderRadius: BorderRadius.circular(24),
|
||
border: Border.all(color: const Color(0xFFFFD54F).withValues(alpha: 0.6), width: 1.5),
|
||
boxShadow: [
|
||
BoxShadow(
|
||
color: const Color(0xFFFFD54F).withValues(alpha: 0.12),
|
||
blurRadius: 18,
|
||
spreadRadius: 2,
|
||
),
|
||
],
|
||
),
|
||
child: Column(
|
||
children: [
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFF382959),
|
||
borderRadius: BorderRadius.circular(10),
|
||
border: Border.all(color: const Color(0xFFFFD54F).withValues(alpha: 0.4)),
|
||
),
|
||
child: Text(
|
||
'ЗНАК #${word.sessionNumber.toString().padLeft(3, '0')}',
|
||
style: const TextStyle(
|
||
color: Color(0xFFFFD54F),
|
||
fontSize: 11,
|
||
fontWeight: FontWeight.w900,
|
||
),
|
||
),
|
||
),
|
||
if (word.category.isNotEmpty)
|
||
Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFF1E243D),
|
||
borderRadius: BorderRadius.circular(10),
|
||
border: Border.all(color: const Color(0xFF80DEEA).withValues(alpha: 0.3)),
|
||
),
|
||
child: Text(
|
||
word.category,
|
||
style: const TextStyle(
|
||
color: Color(0xFF80DEEA),
|
||
fontSize: 11,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 16),
|
||
if (word.emoji.isNotEmpty)
|
||
Text(word.emoji, style: const TextStyle(fontSize: 48)),
|
||
const SizedBox(height: 6),
|
||
Text(
|
||
word.word.toUpperCase(),
|
||
textAlign: TextAlign.center,
|
||
style: const TextStyle(
|
||
color: Color(0xFFFFF9C4),
|
||
fontSize: 30,
|
||
fontWeight: FontWeight.w900,
|
||
letterSpacing: 2.0,
|
||
),
|
||
),
|
||
const SizedBox(height: 8),
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
_buildPill(word.partOfSpeech, const Color(0xFFB388FF)),
|
||
const SizedBox(width: 8),
|
||
_buildPill(word.localizedOperator, const Color(0xFF81C784)),
|
||
const SizedBox(width: 8),
|
||
_buildPill(word.localizedValence, const Color(0xFFFFB74D)),
|
||
],
|
||
),
|
||
const SizedBox(height: 12),
|
||
TextButton.icon(
|
||
style: TextButton.styleFrom(
|
||
foregroundColor: const Color(0xFF80DEEA),
|
||
visualDensity: VisualDensity.compact,
|
||
),
|
||
icon: const Icon(Icons.menu_book_rounded, size: 16),
|
||
label: const Text('Открыть в полном справочнике корней', style: TextStyle(fontSize: 12)),
|
||
onPressed: () {
|
||
Navigator.push(
|
||
context,
|
||
MaterialPageRoute(builder: (_) => SymbolDetailScreen(word: word)),
|
||
);
|
||
},
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildPill(String text, Color color) {
|
||
if (text.isEmpty) return const SizedBox.shrink();
|
||
return Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||
decoration: BoxDecoration(
|
||
color: color.withValues(alpha: 0.12),
|
||
borderRadius: BorderRadius.circular(8),
|
||
border: Border.all(color: color.withValues(alpha: 0.4)),
|
||
),
|
||
child: Text(
|
||
text,
|
||
style: TextStyle(color: color, fontSize: 10, fontWeight: FontWeight.bold),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildMorningFocusTab(WordItem word) {
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||
children: [
|
||
_buildInfoCard(
|
||
title: 'АРХЕТИП И СУТЬ',
|
||
icon: Icons.hub_rounded,
|
||
accentColor: const Color(0xFFFFD54F),
|
||
content: word.core.isNotEmpty ? word.core : word.description,
|
||
),
|
||
const SizedBox(height: 12),
|
||
if (word.oracle.isNotEmpty) ...[
|
||
_buildInfoCard(
|
||
title: 'ПОСЛАНИЕ ОРАКУЛА ДНЯ',
|
||
icon: Icons.auto_awesome_rounded,
|
||
accentColor: const Color(0xFF80DEEA),
|
||
content: word.oracle,
|
||
),
|
||
const SizedBox(height: 12),
|
||
],
|
||
if (word.asks.isNotEmpty) ...[
|
||
Container(
|
||
padding: const EdgeInsets.all(16),
|
||
decoration: BoxDecoration(
|
||
gradient: const LinearGradient(
|
||
colors: [Color(0xFF252044), Color(0xFF181B30)],
|
||
begin: Alignment.topLeft,
|
||
end: Alignment.bottomRight,
|
||
),
|
||
borderRadius: BorderRadius.circular(16),
|
||
border: Border.all(color: const Color(0xFFBA68C8).withValues(alpha: 0.6)),
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
const Row(
|
||
children: [
|
||
Icon(Icons.self_improvement_rounded, size: 18, color: Color(0xFFBA68C8)),
|
||
SizedBox(width: 8),
|
||
Text(
|
||
'КОАН ДЛЯ УМА (ВОПРОС ДНЯ)',
|
||
style: TextStyle(
|
||
color: Color(0xFFBA68C8),
|
||
fontSize: 11,
|
||
fontWeight: FontWeight.w900,
|
||
letterSpacing: 1.0,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 10),
|
||
Text(
|
||
'«${word.asks}»',
|
||
style: const TextStyle(
|
||
color: Color(0xFFFFF9C4),
|
||
fontSize: 14.5,
|
||
fontStyle: FontStyle.italic,
|
||
height: 1.45,
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
const SizedBox(height: 8),
|
||
const Text(
|
||
'Держите этот вопрос в фокусе внимания во время ключевых решений дня.',
|
||
style: TextStyle(color: Color(0xFF8C93B8), fontSize: 11),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget _buildDailyPracticeTab(WordItem word, WordOfDayRecord rec) {
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||
children: [
|
||
Container(
|
||
padding: const EdgeInsets.all(16),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFF151829),
|
||
borderRadius: BorderRadius.circular(18),
|
||
border: Border.all(
|
||
color: rec.isStepCompleted
|
||
? const Color(0xFF81C784)
|
||
: const Color(0xFFFFD54F).withValues(alpha: 0.5),
|
||
width: rec.isStepCompleted ? 1.5 : 1.0,
|
||
),
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
const Row(
|
||
children: [
|
||
Icon(Icons.directions_run_rounded, size: 18, color: Color(0xFFFFD54F)),
|
||
SizedBox(width: 8),
|
||
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: rec.isStepCompleted
|
||
? const Color(0xFF1B3D23)
|
||
: const Color(0xFF262A44),
|
||
borderRadius: BorderRadius.circular(8),
|
||
border: Border.all(
|
||
color: rec.isStepCompleted
|
||
? const Color(0xFF81C784)
|
||
: const Color(0xFF8C93B8).withValues(alpha: 0.4),
|
||
),
|
||
),
|
||
child: Text(
|
||
rec.isStepCompleted ? 'ВЫПОЛНЕНО ✓' : 'К ВЫПОЛНЕНИЮ',
|
||
style: TextStyle(
|
||
color: rec.isStepCompleted
|
||
? const Color(0xFF81C784)
|
||
: const Color(0xFF8C93B8),
|
||
fontSize: 10,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 12),
|
||
Text(
|
||
word.step.isNotEmpty
|
||
? word.step
|
||
: 'Воплотите смысл слова в конкретном материальном поступке или осознанном отказе от автоматизма.',
|
||
style: const TextStyle(
|
||
color: Colors.white,
|
||
fontSize: 14,
|
||
height: 1.45,
|
||
fontWeight: FontWeight.w500,
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
SizedBox(
|
||
width: double.infinity,
|
||
child: ElevatedButton.icon(
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: rec.isStepCompleted
|
||
? const Color(0xFF2E7D32)
|
||
: const Color(0xFF2A2D4A),
|
||
foregroundColor: Colors.white,
|
||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||
),
|
||
icon: Icon(
|
||
rec.isStepCompleted ? Icons.check_circle_rounded : Icons.radio_button_unchecked_rounded,
|
||
color: rec.isStepCompleted ? const Color(0xFFFFF9C4) : const Color(0xFFFFD54F),
|
||
),
|
||
label: Text(
|
||
rec.isStepCompleted
|
||
? 'Практика выполнена (Синхрония закреплена)'
|
||
: 'Отметить шаг как выполненный',
|
||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 13),
|
||
),
|
||
onPressed: () => _toggleStep(!rec.isStepCompleted),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(height: 14),
|
||
if (word.shadow.isNotEmpty) ...[
|
||
Container(
|
||
padding: const EdgeInsets.all(16),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFF1E1724),
|
||
borderRadius: BorderRadius.circular(16),
|
||
border: Border.all(color: const Color(0xFFFF7043).withValues(alpha: 0.5)),
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
const Row(
|
||
children: [
|
||
Icon(Icons.warning_amber_rounded, size: 18, color: Color(0xFFFF7043)),
|
||
SizedBox(width: 8),
|
||
Text(
|
||
'ТЕНЕВАЯ ЛОВУШКА ДНЯ',
|
||
style: TextStyle(
|
||
color: Color(0xFFFF7043),
|
||
fontSize: 11,
|
||
fontWeight: FontWeight.w900,
|
||
letterSpacing: 1.0,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 8),
|
||
Text(
|
||
word.shadow,
|
||
style: const TextStyle(
|
||
color: Color(0xFFFFCCBC),
|
||
fontSize: 13,
|
||
height: 1.4,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget _buildEveningDiaryTab(WordOfDayRecord rec) {
|
||
return Container(
|
||
padding: const EdgeInsets.all(16),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFF151829),
|
||
borderRadius: BorderRadius.circular(18),
|
||
border: Border.all(color: const Color(0xFF2A2D4A)),
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
const Row(
|
||
children: [
|
||
Icon(Icons.edit_note_rounded, size: 20, color: Color(0xFF80DEEA)),
|
||
SizedBox(width: 8),
|
||
Text(
|
||
'ДНЕВНИК СИНХРОНИЙ И НАБЛЮДЕНИЙ',
|
||
style: TextStyle(
|
||
color: Color(0xFF80DEEA),
|
||
fontSize: 11,
|
||
fontWeight: FontWeight.w900,
|
||
letterSpacing: 1.0,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 8),
|
||
const Text(
|
||
'Зафиксируйте знаки, сны, диалоги или события, через которые слово дня проявилось в вашей жизни:',
|
||
style: TextStyle(color: Color(0xFF8C93B8), fontSize: 12.5, height: 1.35),
|
||
),
|
||
const SizedBox(height: 12),
|
||
TextField(
|
||
controller: _diaryController,
|
||
maxLines: 4,
|
||
style: const TextStyle(color: Colors.white, fontSize: 13.5),
|
||
decoration: InputDecoration(
|
||
hintText: 'Сегодня я заметил(а), что слово проявилось в...',
|
||
hintStyle: const TextStyle(color: Color(0xFF636A8A), fontSize: 12.5),
|
||
filled: true,
|
||
fillColor: const Color(0xFF1B1E33),
|
||
border: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(12),
|
||
borderSide: const BorderSide(color: Color(0xFF2C3252)),
|
||
),
|
||
enabledBorder: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(12),
|
||
borderSide: const BorderSide(color: Color(0xFF2C3252)),
|
||
),
|
||
focusedBorder: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(12),
|
||
borderSide: const BorderSide(color: Color(0xFF80DEEA)),
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(height: 12),
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
if (_isDiarySaved)
|
||
const Row(
|
||
children: [
|
||
Icon(Icons.check_circle_rounded, size: 16, color: Color(0xFF81C784)),
|
||
SizedBox(width: 6),
|
||
Text('Сохранено', style: TextStyle(color: Color(0xFF81C784), fontSize: 12)),
|
||
],
|
||
)
|
||
else
|
||
const SizedBox.shrink(),
|
||
ElevatedButton.icon(
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: const Color(0xFF80DEEA),
|
||
foregroundColor: const Color(0xFF0C0E1A),
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||
),
|
||
icon: const Icon(Icons.save_rounded, size: 16),
|
||
label: const Text('Сохранить заметку', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12)),
|
||
onPressed: _saveDiary,
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildActionRowSingle(WordItem word) {
|
||
final persona = _settingsRepo.persona;
|
||
|
||
return Row(
|
||
children: [
|
||
Expanded(
|
||
child: ElevatedButton.icon(
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: const Color(0xFF262042),
|
||
foregroundColor: const Color(0xFFFFF9C4),
|
||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(14),
|
||
side: BorderSide(color: const Color(0xFFFFD54F).withValues(alpha: 0.4)),
|
||
),
|
||
),
|
||
icon: const Icon(Icons.psychology_rounded, size: 18, color: Color(0xFFFFD54F)),
|
||
label: Text(
|
||
'Оракул (${persona.title.split(' ').first})',
|
||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
|
||
),
|
||
onPressed: () => _consultOracleSingle(word),
|
||
),
|
||
),
|
||
const SizedBox(width: 10),
|
||
Expanded(
|
||
child: ElevatedButton.icon(
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: const Color(0xFF1E283D),
|
||
foregroundColor: const Color(0xFF80DEEA),
|
||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(14),
|
||
side: BorderSide(color: const Color(0xFF80DEEA).withValues(alpha: 0.4)),
|
||
),
|
||
),
|
||
icon: const Icon(Icons.merge_rounded, size: 18),
|
||
label: const Text(
|
||
'В Сессию Дня',
|
||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
|
||
),
|
||
onPressed: () => _weaveIntoSyncreticSessionSingle(word),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget _buildActionRowTriptych(WordItem morning, WordItem day, WordItem evening) {
|
||
final persona = _settingsRepo.persona;
|
||
|
||
return Row(
|
||
children: [
|
||
Expanded(
|
||
child: ElevatedButton.icon(
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: const Color(0xFF262042),
|
||
foregroundColor: const Color(0xFFFFF9C4),
|
||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(14),
|
||
side: BorderSide(color: const Color(0xFFFFD54F).withValues(alpha: 0.4)),
|
||
),
|
||
),
|
||
icon: const Icon(Icons.psychology_rounded, size: 18, color: Color(0xFFFFD54F)),
|
||
label: Text(
|
||
'Синтез Триады (${persona.title.split(' ').first})',
|
||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
|
||
),
|
||
onPressed: () => _consultOracleTriptych(morning, day, evening),
|
||
),
|
||
),
|
||
const SizedBox(width: 10),
|
||
Expanded(
|
||
child: ElevatedButton.icon(
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: const Color(0xFF1E283D),
|
||
foregroundColor: const Color(0xFF80DEEA),
|
||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(14),
|
||
side: BorderSide(color: const Color(0xFF80DEEA).withValues(alpha: 0.4)),
|
||
),
|
||
),
|
||
icon: const Icon(Icons.merge_rounded, size: 18),
|
||
label: const Text(
|
||
'Вплести Триаду',
|
||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
|
||
),
|
||
onPressed: () => _weaveTriptychIntoSyncreticSession(morning, day, evening),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget _buildAiAdviceCard() {
|
||
final persona = _settingsRepo.persona;
|
||
|
||
return Container(
|
||
padding: const EdgeInsets.all(16),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFF1A1C30),
|
||
borderRadius: BorderRadius.circular(18),
|
||
border: Border.all(color: const Color(0xFFFFD54F).withValues(alpha: 0.5)),
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Row(
|
||
children: [
|
||
const GradientStar(size: 16),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
child: Text(
|
||
'НАСТАВЛЕНИЕ: ${persona.title.toUpperCase()}',
|
||
style: const TextStyle(
|
||
color: Color(0xFFFFD54F),
|
||
fontSize: 11,
|
||
fontWeight: FontWeight.w900,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 10),
|
||
Text(
|
||
_aiAdvice!,
|
||
style: const TextStyle(
|
||
color: Color(0xFFECEFF1),
|
||
fontSize: 13,
|
||
height: 1.45,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildInfoCard({
|
||
required String title,
|
||
required IconData icon,
|
||
required Color accentColor,
|
||
required String content,
|
||
}) {
|
||
return Container(
|
||
padding: const EdgeInsets.all(16),
|
||
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: [
|
||
Icon(icon, size: 16, color: accentColor),
|
||
const SizedBox(width: 8),
|
||
Text(
|
||
title,
|
||
style: TextStyle(
|
||
color: accentColor,
|
||
fontSize: 11,
|
||
fontWeight: FontWeight.w900,
|
||
letterSpacing: 1.0,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 10),
|
||
Text(
|
||
content,
|
||
style: const TextStyle(
|
||
color: Color(0xFFECEFF1),
|
||
fontSize: 13.5,
|
||
height: 1.4,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|