Initial commit

This commit is contained in:
2026-04-24 23:38:00 -05:00
commit 80f936febc
219 changed files with 6789 additions and 0 deletions
+598
View File
@@ -0,0 +1,598 @@
import 'dart:convert';
import 'dart:math' as math;
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flip_card/flip_card.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:intl/intl.dart';
import 'package:screenshot/screenshot.dart';
import 'package:share_plus/share_plus.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'firebase_options.dart';
import 'models/tarot_card.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
try {
await FirebaseAuth.instance.signInAnonymously();
} catch (e) {
debugPrint("Auth Error: $e");
}
runApp(const TarotApp());
}
class TarotSpread {
final String title;
final String description;
final List<String> labels;
final int count;
TarotSpread({required this.title, required this.description, required this.labels, required this.count});
}
class TarotApp extends StatelessWidget {
const TarotApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Pocket Oracle',
debugShowCheckedModeBanner: false,
theme: ThemeData(
brightness: Brightness.dark,
primaryColor: Colors.deepPurple,
fontFamily: 'Metamorphous',
scaffoldBackgroundColor: const Color(0xFF0F0C29),
appBarTheme: const AppBarTheme(backgroundColor: Colors.transparent, elevation: 0, centerTitle: true),
),
home: const MainNavigation(),
);
}
}
// --- PARTICLE BACKGROUND ---
class StarfieldBackground extends StatefulWidget {
final Widget child;
const StarfieldBackground({super.key, required this.child});
@override
State<StarfieldBackground> createState() => _StarfieldBackgroundState();
}
class _StarfieldBackgroundState extends State<StarfieldBackground> with SingleTickerProviderStateMixin {
late AnimationController _controller;
final List<math.Point> stars = List.generate(45, (i) => math.Point(math.Random().nextDouble(), math.Random().nextDouble()));
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(seconds: 25))..repeat();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFF0F0C29), Color(0xFF302B63), Color(0xFF24243E)],
),
),
),
AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return CustomPaint(painter: StarPainter(stars, _controller.value), size: Size.infinite);
},
),
widget.child,
],
);
}
}
class StarPainter extends CustomPainter {
final List<math.Point> stars;
final double animationValue;
StarPainter(this.stars, this.animationValue);
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = Colors.white;
for (var star in stars) {
double x = star.x * size.width;
double y = (star.y * size.height + (animationValue * 80)) % size.height;
double opacity = (math.sin(animationValue * math.pi * 4 + star.x * 20) + 1) / 2;
paint.color = Colors.white.withValues(alpha: opacity * 0.3);
canvas.drawCircle(Offset(x, y), 1.2 * star.x + 0.5, paint);
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
// --- MAIN HUB ---
class MainNavigation extends StatefulWidget {
const MainNavigation({super.key});
@override
State<MainNavigation> createState() => _MainNavigationState();
}
class _MainNavigationState extends State<MainNavigation> {
int _selectedIndex = 0;
String _selectedCardBack = 'assets/images/card_back.jpg';
@override
void initState() {
super.initState();
_loadSettings();
}
_loadSettings() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_selectedCardBack = prefs.getString('card_back') ?? 'assets/images/card_back.jpg';
});
}
_updateCardBack(String path) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('card_back', path);
setState(() => _selectedCardBack = path);
}
@override
Widget build(BuildContext context) {
final List<Widget> screens = [
DeckScreen(cardBack: _selectedCardBack),
DailyCardScreen(cardBack: _selectedCardBack),
const LibraryScreen(),
const JournalScreen(),
SettingsScreen(currentBack: _selectedCardBack, onBackChanged: _updateCardBack),
];
return StarfieldBackground(
child: Scaffold(
backgroundColor: Colors.transparent,
body: IndexedStack(index: _selectedIndex, children: screens),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedIndex,
onTap: (index) => setState(() => _selectedIndex = index),
backgroundColor: const Color(0xFF16213E).withValues(alpha: 0.9),
selectedItemColor: Colors.amberAccent,
unselectedItemColor: Colors.white54,
type: BottomNavigationBarType.fixed,
items: const [
BottomNavigationBarItem(icon: Icon(Icons.auto_awesome), label: "Oracle"),
BottomNavigationBarItem(icon: Icon(Icons.today), label: "Daily"),
BottomNavigationBarItem(icon: Icon(Icons.menu_book), label: "Library"),
BottomNavigationBarItem(icon: Icon(Icons.history_edu), label: "Journal"),
BottomNavigationBarItem(icon: Icon(Icons.settings), label: "Settings"),
],
),
),
);
}
}
// --- TAB 1: ORACLE ---
class DeckScreen extends StatefulWidget {
final String cardBack;
const DeckScreen({super.key, required this.cardBack});
@override
State<DeckScreen> createState() => _DeckScreenState();
}
class _DeckScreenState extends State<DeckScreen> {
final AudioPlayer _audioPlayer = AudioPlayer();
final ScreenshotController _screenshotController = ScreenshotController();
List<TarotCard> deck = [];
List<TarotCard> currentSpread = [];
List<bool> isRevealed = [];
List<bool> orientationIsReversed = [];
List<GlobalKey<FlipCardState>> cardKeys = [];
TarotSpread? activeSpread;
bool isLoading = true;
bool isSaving = false;
final PageController _pageController = PageController(viewportFraction: 0.85);
final List<TarotSpread> spreads = [
TarotSpread(title: "DAILY PULL", description: "Single card guidance", labels: ["GUIDANCE"], count: 1),
TarotSpread(title: "3-CARD SPREAD", description: "Past, Present, and Future", labels: ["PAST", "PRESENT", "FUTURE"], count: 3),
TarotSpread(title: "HORSESHOE", description: "7-card deep dive", labels: ["PAST", "PRESENT", "HIDDEN", "OBSTACLE", "EXTERNAL", "ADVICE", "OUTCOME"], count: 7),
TarotSpread(title: "CELTIC CROSS", description: "10-card classic reading", labels: ["PRESENT", "CHALLENGE", "PAST", "FUTURE", "CONSCIOUS", "SUB-CON", "ADVICE", "EXTERNAL", "HOPES/FEARS", "OUTCOME"], count: 10),
TarotSpread(title: "BIRTH CARD", description: "Numerology path", labels: [], count: 0),
];
@override
void initState() { super.initState(); _loadDeck(); }
_loadDeck() async {
final String response = await rootBundle.loadString('assets/tarot_data.json');
final List<dynamic> data = json.decode(response);
if (!mounted) return;
setState(() {
deck = data.map((cardJson) => TarotCard.fromJson(cardJson)).toList();
isLoading = false;
});
}
void _drawCards(TarotSpread spread) async {
if (spread.count == 0) {
Navigator.push(context, MaterialPageRoute(builder: (context) => BirthCardScreen(allCards: deck)));
return;
}
HapticFeedback.mediumImpact();
await _audioPlayer.play(AssetSource('sounds/shuffle.wav'));
setState(() {
activeSpread = spread;
isRevealed = List.generate(spread.count, (_) => false);
orientationIsReversed = List.generate(spread.count, (_) => math.Random().nextBool());
currentSpread = [];
});
await Future.delayed(const Duration(milliseconds: 400));
if (!mounted) return;
setState(() {
deck.shuffle();
currentSpread = deck.take(spread.count).toList();
cardKeys = List.generate(spread.count, (_) => GlobalKey<FlipCardState>());
});
}
void _shareSpread() async {
final image = await _screenshotController.capture();
if (image != null) {
final directory = await getApplicationDocumentsDirectory();
final imagePath = await File('${directory.path}/tarot_reading.png').create();
await imagePath.writeAsBytes(image);
await Share.shareXFiles([XFile(imagePath.path)], text: 'My reading from Pocket Oracle ✨');
}
}
Future<void> _saveReading() async {
setState(() => isSaving = true);
final user = FirebaseAuth.instance.currentUser;
if (user == null) return;
List<String> entries = currentSpread.asMap().entries.map((e) => "${e.value.name} ${orientationIsReversed[e.key] ? '(R)' : '(U)'}").toList();
await FirebaseFirestore.instance.collection('users').doc(user.uid).collection('readings').add({
'timestamp': FieldValue.serverTimestamp(),
'spreadTitle': activeSpread?.title,
'cardNames': entries,
});
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Reading Saved!")));
setState(() => isSaving = false);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.transparent,
appBar: AppBar(
title: const Text("POCKET ORACLE", style: TextStyle(letterSpacing: 4)),
actions: [
if (currentSpread.isNotEmpty) IconButton(icon: const Icon(Icons.share), onPressed: _shareSpread),
if (currentSpread.isNotEmpty) IconButton(icon: const Icon(Icons.refresh), onPressed: () => setState(() => currentSpread = [])),
],
),
body: isLoading ? const Center(child: CircularProgressIndicator()) :
currentSpread.isEmpty ? _buildSelection() : _buildReading(),
);
}
Widget _buildSelection() {
return Center(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: spreads.map((s) => Padding(
padding: const EdgeInsets.only(bottom: 12),
child: SizedBox(width: 320, child: ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: const Color(0xFF16213E).withValues(alpha: 0.8), padding: const EdgeInsets.all(18), side: const BorderSide(color: Colors.amberAccent, width: 0.5), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))),
onPressed: () => _drawCards(s),
child: Column(children: [Text(s.title, style: const TextStyle(color: Colors.amberAccent, fontWeight: FontWeight.bold)), Text(s.description, style: const TextStyle(fontSize: 10, color: Colors.white38))]),
)),
)).toList(),
),
),
);
}
Widget _buildReading() {
return Column(
children: [
Expanded(
child: Screenshot(
controller: _screenshotController,
child: Container(
color: const Color(0xFF0F0C29),
child: PageView.builder(
controller: _pageController,
itemCount: currentSpread.length,
itemBuilder: (context, index) => _buildCardPage(index),
),
),
),
),
if (isRevealed.isNotEmpty && isRevealed.every((r) => r == true))
Padding(
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 20),
child: SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: isSaving ? null : _saveReading,
style: ElevatedButton.styleFrom(backgroundColor: Colors.deepPurple, padding: const EdgeInsets.all(15)),
child: Text(isSaving ? "SAVING..." : "SAVE TO JOURNAL")
),
),
),
TextButton.icon(onPressed: () => setState(() => currentSpread = []), icon: const Icon(Icons.close), label: const Text("EXIT")),
],
);
}
Widget _buildCardPage(int index) {
final card = currentSpread[index];
final rev = orientationIsReversed[index];
return SingleChildScrollView(
child: Column(
children: [
const SizedBox(height: 10),
Text(activeSpread!.labels[index], style: const TextStyle(color: Colors.amberAccent, letterSpacing: 4, fontWeight: FontWeight.bold)),
const SizedBox(height: 15),
FlipCard(
key: cardKeys[index],
onFlip: () { HapticFeedback.lightImpact(); _audioPlayer.play(AssetSource('sounds/flip.wav')); },
onFlipDone: (isFront) => setState(() => isRevealed[index] = !isFront),
front: _cardImg(widget.cardBack, Colors.black54),
back: Transform.rotate(angle: rev ? math.pi : 0, child: _cardImg(card.imagePath, Colors.amberAccent)),
),
const SizedBox(height: 20),
AnimatedOpacity(
opacity: isRevealed[index] ? 1.0 : 0.0,
duration: const Duration(milliseconds: 500),
child: Column(children: [
Text(card.name + (rev ? " (REV)" : ""), style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.amberAccent)),
Padding(padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 10), child: Text(rev ? card.meaningReversed : card.meaningUpright, textAlign: TextAlign.center, style: const TextStyle(fontSize: 14, color: Colors.white70))),
]),
),
],
),
);
}
Widget _cardImg(String path, Color shadow) {
return Container(
decoration: BoxDecoration(boxShadow: [BoxShadow(color: shadow.withValues(alpha: 0.3), blurRadius: 30)]),
child: ClipRRect(borderRadius: BorderRadius.circular(15), child: Image.asset(path, height: 400, fit: BoxFit.contain)),
);
}
@override
void dispose() { _audioPlayer.dispose(); _pageController.dispose(); super.dispose(); }
}
// --- TAB 2: DAILY CARD ---
class DailyCardScreen extends StatefulWidget {
final String cardBack;
const DailyCardScreen({super.key, required this.cardBack});
@override
State<DailyCardScreen> createState() => _DailyCardScreenState();
}
class _DailyCardScreenState extends State<DailyCardScreen> {
TarotCard? dailyCard;
bool revealed = false;
@override
void initState() { super.initState(); _loadDaily(); }
_loadDaily() async {
final String res = await rootBundle.loadString('assets/tarot_data.json');
final List<dynamic> data = json.decode(res);
final now = DateTime.now();
final seed = now.year * 10000 + now.month * 100 + now.day;
final r = math.Random(seed);
setState(() => dailyCard = TarotCard.fromJson(data[r.nextInt(data.length)]));
}
@override
Widget build(BuildContext context) {
return Center(
child: dailyCard == null ? const CircularProgressIndicator() : Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text("TODAY'S ENERGY", style: TextStyle(letterSpacing: 2, color: Colors.white38)),
const SizedBox(height: 30),
FlipCard(
onFlipDone: (isFront) => setState(() => revealed = !isFront),
front: _cardImg(widget.cardBack, Colors.black),
back: _cardImg(dailyCard!.imagePath, Colors.amberAccent),
),
const SizedBox(height: 30),
if (revealed) Text(dailyCard!.name.toUpperCase(), style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Colors.amberAccent)),
if (revealed) Padding(padding: const EdgeInsets.symmetric(horizontal: 40), child: Text(dailyCard!.meaningUpright, textAlign: TextAlign.center)),
],
),
);
}
Widget _cardImg(String path, Color shadow) {
return Container(decoration: BoxDecoration(boxShadow: [BoxShadow(color: shadow.withValues(alpha: 0.2), blurRadius: 30)]), child: ClipRRect(borderRadius: BorderRadius.circular(20), child: Image.asset(path, height: 400)));
}
}
// --- TAB 3: LIBRARY (FIXED FILTERING) ---
class LibraryScreen extends StatefulWidget {
const LibraryScreen({super.key});
@override
State<LibraryScreen> createState() => _LibraryScreenState();
}
class _LibraryScreenState extends State<LibraryScreen> {
List<TarotCard> all = [];
List<TarotCard> display = [];
String query = "";
String selectedSuit = "All";
final List<String> suits = ["All", "Major Arcana", "Cups", "Swords", "Wands", "Pentacles"];
@override
void initState() { super.initState(); _load(); }
_load() async {
final String res = await rootBundle.loadString('assets/tarot_data.json');
final List<dynamic> data = json.decode(res);
setState(() {
all = data.map((c) => TarotCard.fromJson(c)).toList();
display = all;
});
}
void _filter() {
setState(() {
display = all.where((c) {
final matchesSearch = c.name.toLowerCase().contains(query.toLowerCase());
// FIXED: Case-insensitive comparison for arcana/suit field
final matchesSuit = (selectedSuit == "All") ||
(c.arcana.toLowerCase() == selectedSuit.toLowerCase()) ||
(selectedSuit == "Major Arcana" && c.arcana.toLowerCase().contains("major"));
return matchesSearch && matchesSuit;
}).toList();
});
}
@override
Widget build(BuildContext context) {
return Column(children: [
const SizedBox(height: 50),
Padding(padding: const EdgeInsets.all(12), child: TextField(onChanged: (v) { query = v; _filter(); }, decoration: InputDecoration(hintText: "Search cards...", prefixIcon: const Icon(Icons.search), filled: true, fillColor: const Color(0xFF16213E).withValues(alpha: 0.8), border: OutlineInputBorder(borderRadius: BorderRadius.circular(15), borderSide: BorderSide.none)))),
SizedBox(height: 40, child: ListView.builder(scrollDirection: Axis.horizontal, itemCount: suits.length, itemBuilder: (context, i) => Padding(padding: const EdgeInsets.symmetric(horizontal: 5), child: ChoiceChip(label: Text(suits[i]), selected: selectedSuit == suits[i], onSelected: (s) { setState(() { selectedSuit = suits[i]; _filter(); }); })))),
Expanded(child: GridView.builder(padding: const EdgeInsets.all(15), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 0.6, crossAxisSpacing: 10, mainAxisSpacing: 10), itemCount: display.length, itemBuilder: (context, i) => GestureDetector(onTap: () => Navigator.push(context, MaterialPageRoute(builder: (c) => CardDetailScreen(card: display[i]))), child: Hero(tag: 'lib_${display[i].id}', child: ClipRRect(borderRadius: BorderRadius.circular(10), child: Image.asset(display[i].imagePath, fit: BoxFit.cover)))))),
]);
}
}
class CardDetailScreen extends StatelessWidget {
final TarotCard card;
const CardDetailScreen({super.key, required this.card});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(card.name)),
body: SingleChildScrollView(child: Column(children: [
const SizedBox(height: 20),
Hero(tag: 'lib_${card.id}', child: Center(child: ClipRRect(borderRadius: BorderRadius.circular(20), child: Image.asset(card.imagePath, height: 400)))),
Padding(padding: const EdgeInsets.all(30), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(card.arcana.toUpperCase(), style: const TextStyle(color: Colors.amberAccent, fontWeight: FontWeight.bold, letterSpacing: 2)),
Text(card.name, style: const TextStyle(fontSize: 32, fontWeight: FontWeight.bold)),
const Divider(height: 40),
const Text("UPRIGHT", style: TextStyle(color: Colors.greenAccent, fontWeight: FontWeight.bold)),
Text(card.meaningUpright, style: const TextStyle(fontSize: 16, height: 1.5)),
const SizedBox(height: 30),
const Text("REVERSED", style: TextStyle(color: Colors.redAccent, fontWeight: FontWeight.bold)),
Text(card.meaningReversed, style: const TextStyle(fontSize: 16, height: 1.5)),
])),
])),
);
}
}
// --- TAB 4: JOURNAL ---
class JournalScreen extends StatelessWidget {
const JournalScreen({super.key});
@override
Widget build(BuildContext context) {
final user = FirebaseAuth.instance.currentUser;
return Scaffold(
backgroundColor: Colors.transparent,
appBar: AppBar(title: const Text("MY JOURNAL")),
body: user == null ? const Center(child: CircularProgressIndicator()) : StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance.collection('users').doc(user.uid).collection('readings').orderBy('timestamp', descending: true).snapshots(),
builder: (context, snap) {
if (!snap.hasData || snap.data!.docs.isEmpty) return const Center(child: Text("Empty Journal."));
return ListView.builder(
itemCount: snap.data!.docs.length,
itemBuilder: (context, i) {
var doc = snap.data!.docs[i];
var data = doc.data() as Map<String, dynamic>;
DateTime date = (data['timestamp'] as Timestamp?)?.toDate() ?? DateTime.now();
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
color: const Color(0xFF16213E).withValues(alpha: 0.8),
child: ListTile(
title: Text(data['spreadTitle'] ?? "Reading", style: const TextStyle(color: Colors.amberAccent)),
subtitle: Text("${(data['cardNames'] as List).join(', ')}\n${DateFormat('MMM dd, hh:mm a').format(date)}"),
),
);
},
);
},
),
);
}
}
// --- TAB 5: SETTINGS ---
class SettingsScreen extends StatelessWidget {
final String currentBack;
final Function(String) onBackChanged;
const SettingsScreen({super.key, required this.currentBack, required this.onBackChanged});
@override
Widget build(BuildContext context) {
final backs = ['assets/images/card_back.jpg', 'assets/images/card_back_alt1.jpg', 'assets/images/card_back_alt2.jpg', 'assets/images/card_back_alt3.jpg'];
return Padding(padding: const EdgeInsets.all(20.0), child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
const Text("CHOOSE YOUR DECK BACK", style: TextStyle(color: Colors.amberAccent, letterSpacing: 2, fontWeight: FontWeight.bold)),
const SizedBox(height: 40),
SizedBox(height: 200, child: ListView.builder(scrollDirection: Axis.horizontal, itemCount: backs.length, itemBuilder: (context, i) => GestureDetector(
onTap: () => onBackChanged(backs[i]),
child: Container(margin: const EdgeInsets.only(right: 15), decoration: BoxDecoration(border: Border.all(color: currentBack == backs[i] ? Colors.amberAccent : Colors.white10, width: 2), borderRadius: BorderRadius.circular(10)), child: ClipRRect(borderRadius: BorderRadius.circular(8), child: Image.asset(backs[i], width: 120, fit: BoxFit.cover))),
))),
]));
}
}
// --- BIRTH CARD ---
class BirthCardScreen extends StatefulWidget {
final List<TarotCard> allCards;
const BirthCardScreen({super.key, required this.allCards});
@override
State<BirthCardScreen> createState() => _BirthCardScreenState();
}
class _BirthCardScreenState extends State<BirthCardScreen> {
TarotCard? res;
_calc(DateTime d) {
int sum = (d.year ~/ 100) + (d.year % 100) + d.month + d.day;
int r = sum;
while (r > 21) {
int t = 0;
for (var c in r.toString().split('')) { t += int.parse(c); }
r = t;
}
setState(() => res = widget.allCards.firstWhere((c) => c.id == r));
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFF0F0C29),
appBar: AppBar(title: const Text("BIRTH CARD")),
body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
if (res == null) ElevatedButton(onPressed: () async {
DateTime? p = await showDatePicker(context: context, initialDate: DateTime(2000), firstDate: DateTime(1900), lastDate: DateTime.now());
if (p != null) _calc(p);
}, child: const Text("SELECT BIRTHDAY"))
else ...[
Hero(tag: 'lib_${res!.id}', child: Image.asset(res!.imagePath, height: 350)),
const SizedBox(height: 20),
Text(res!.name.toUpperCase(), style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Colors.amberAccent)),
Padding(padding: const EdgeInsets.all(20), child: Text(res!.meaningUpright, textAlign: TextAlign.center)),
TextButton(onPressed: () => setState(() => res = null), child: const Text("RESET")),
]
])),
);
}
}