feat: initial release of Oracle 999 divination app

This commit is contained in:
Alma Alexyz
2026-08-31 12:35:13 +05:00
commit 28b85061b9
143 changed files with 16073 additions and 0 deletions
+268
View File
@@ -0,0 +1,268 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../models/history_entry_model.dart';
import '../services/words_repository.dart';
import '../widgets/gradient_star_badge.dart';
class HistoryScreen extends StatefulWidget {
const HistoryScreen({super.key});
@override
State<HistoryScreen> createState() => _HistoryScreenState();
}
class _HistoryScreenState extends State<HistoryScreen> {
final _repo = WordsRepository();
void _confirmClearHistory() {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: const Row(
children: [
GradientStar(size: 20),
SizedBox(width: 8),
Text('Очистить историю?'),
],
),
content: const Text(
'Все сохранённые записи гаданий и жребиев с датами будут удалены.',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
child: const Text('Отмена'),
),
FilledButton(
onPressed: () async {
Navigator.pop(ctx);
await _repo.clearHistory();
setState(() {});
HapticFeedback.mediumImpact();
},
style: FilledButton.styleFrom(backgroundColor: Colors.red.shade800),
child: const Text('Очистить'),
),
],
),
);
}
void _copyEntry(HistoryEntry entry) {
final text =
'✨ Оракул 999 — Запись из Истории ✨\n'
'Время: ${entry.formattedDate}\n'
'Тип: ${entry.readingType}${entry.positionTitle != null ? ' (${entry.positionTitle})' : ''}\n'
'Знак №${entry.formattedSessionNumber}: ${entry.word.toUpperCase()}\n'
'Часть речи: ${entry.partOfSpeech}\n\n'
'🔮 Толкование:\n${entry.symbolicMeaning}';
Clipboard.setData(ClipboardData(text: text));
HapticFeedback.lightImpact();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Row(
children: [
GradientStar(size: 18),
SizedBox(width: 10),
Expanded(child: Text('Запись скопирована в буфер обмена')),
],
),
duration: const Duration(seconds: 2),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
);
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final isDark = theme.brightness == Brightness.dark;
final historyList = _repo.history;
return Scaffold(
appBar: AppBar(
title: const Row(
children: [
GradientStar(size: 24),
SizedBox(width: 10),
Text(
'История гаданий',
style: TextStyle(fontWeight: FontWeight.w900),
),
],
),
actions: [
if (historyList.isNotEmpty)
IconButton(
tooltip: 'Очистить историю',
icon: const Icon(Icons.delete_outline_rounded),
onPressed: _confirmClearHistory,
),
],
),
body: historyList.isEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const GradientStar(size: 48),
const SizedBox(height: 16),
Text(
'История пока пуста',
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Text(
'Совершите жребий или сохраните выпавший символ, и он появится здесь с точной датой и временем.',
textAlign: TextAlign.center,
style: TextStyle(color: theme.colorScheme.outline),
),
),
],
),
)
: ListView.separated(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
itemCount: historyList.length,
separatorBuilder: (_, _) => const SizedBox(height: 10),
itemBuilder: (context, index) {
final item = historyList[index];
return Card(
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
side: BorderSide(
color: const Color(0xFFFFD54F).withValues(alpha: 0.2),
),
),
color: isDark ? const Color(0xFF1B1D30) : theme.colorScheme.surface,
child: Padding(
padding: const EdgeInsets.all(14),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Top header: Timestamp and Reading type
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
const Icon(
Icons.access_time_rounded,
size: 14,
color: Color(0xFFFFD54F),
),
const SizedBox(width: 5),
Text(
item.formattedDate,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFFFFD54F),
),
),
],
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: const Color(0xFF2A2D4A),
borderRadius: BorderRadius.circular(8),
),
child: Text(
item.readingType,
style: const TextStyle(
fontSize: 11,
color: Color(0xFFFFF9C4),
fontWeight: FontWeight.w600,
),
),
),
],
),
if (item.positionTitle != null) ...[
const SizedBox(height: 6),
Text(
item.positionTitle!,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: theme.colorScheme.primary,
),
),
],
const SizedBox(height: 10),
// Word row with number badge
Row(
children: [
GradientStarBadge(
text: '#${item.formattedSessionNumber}',
size: 14,
),
const SizedBox(width: 10),
Expanded(
child: Text(
item.word.toUpperCase(),
style: const TextStyle(
fontWeight: FontWeight.w900,
fontSize: 18,
letterSpacing: 0.8,
),
),
),
IconButton(
icon: const Icon(Icons.copy_rounded, size: 18),
tooltip: 'Скопировать',
onPressed: () => _copyEntry(item),
),
IconButton(
icon: const Icon(Icons.delete_outline, size: 18),
tooltip: 'Удалить',
onPressed: () async {
await _repo.deleteHistoryEntry(item.id);
setState(() {});
},
),
],
),
const SizedBox(height: 8),
// Meaning snippet
Container(
width: double.infinity,
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: isDark
? const Color(0xFF131524)
: theme.colorScheme.surfaceContainerLow,
borderRadius: BorderRadius.circular(12),
),
child: Text(
item.symbolicMeaning,
style: TextStyle(
fontSize: 13,
height: 1.4,
color: isDark ? const Color(0xFFD4D6E6) : Colors.black87,
),
),
),
],
),
),
);
},
),
);
}
}