532 lines
16 KiB
Dart
532 lines
16 KiB
Dart
import 'dart:math';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:url_launcher/url_launcher.dart';
|
||
|
||
import '../models/school.dart';
|
||
import 'lessons_page.dart';
|
||
import 'select_school_page.dart';
|
||
import 'meditation_page.dart';
|
||
import '../services/vanguard_api.dart';
|
||
import 'login_page.dart';
|
||
|
||
class HomePage extends StatefulWidget {
|
||
final String token;
|
||
final School school;
|
||
final String? userFirstName;
|
||
|
||
const HomePage({
|
||
super.key,
|
||
required this.token,
|
||
required this.school,
|
||
this.userFirstName,
|
||
});
|
||
|
||
@override
|
||
State<HomePage> createState() => _HomePageState();
|
||
}
|
||
|
||
class _HomePageState extends State<HomePage> {
|
||
static const Color kBg = Color(0xFFF6F6FB);
|
||
static const Color kGreen = Color(0xFF10B981);
|
||
|
||
int bottomIndex = 0; // Home
|
||
|
||
late final String zenQuote;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
zenQuote = _pickZenQuote();
|
||
}
|
||
|
||
String get _name => (widget.userFirstName ?? '').trim();
|
||
String get _avatarLetter => _name.isNotEmpty ? _name[0].toUpperCase() : 'U';
|
||
|
||
String get _schoolAddress => (widget.school.addressFull ?? '').trim();
|
||
|
||
String _pickZenQuote() {
|
||
const quotes = <String>[
|
||
'Respira. Sei esattamente dove devi essere.',
|
||
'Un respiro alla volta, un passo alla volta.',
|
||
'La costanza è più forte della motivazione.',
|
||
'Lascia andare ciò che non serve.',
|
||
'La calma è una superpotenza.',
|
||
'Il corpo ascolta ciò che la mente ripete.',
|
||
'Presenza. Non perfezione.',
|
||
'Sii gentile con te stesso: è pratica.',
|
||
'Ogni pratica è un nuovo inizio.',
|
||
'Dove va il respiro, va l’attenzione.',
|
||
'Inspira fiducia, espira paura.',
|
||
'La pace inizia dal respiro.',
|
||
'Scegli la lentezza, trovi chiarezza.',
|
||
'Anche il silenzio insegna.',
|
||
'Oggi basta esserci.',
|
||
'Il meglio è nel semplice.',
|
||
'Radicati, poi fiorisci.',
|
||
'Lascia che il respiro ti guidi.',
|
||
'Un minuto di presenza cambia la giornata.',
|
||
'La forza è morbida.',
|
||
'Il cambiamento nasce dall’ascolto.',
|
||
'Non correre: senti.',
|
||
'Ogni espirazione è un rilascio.',
|
||
'Ogni inspirazione è un dono.',
|
||
'La pratica è un ritorno a casa.',
|
||
'Dove c’è respiro, c’è spazio.',
|
||
'Trasforma la tensione in attenzione.',
|
||
'Sii saldo e leggero.',
|
||
'Sii stabile come una montagna.',
|
||
'Sii fluido come l’acqua.',
|
||
'La quiete è una scelta.',
|
||
'Concediti tempo.',
|
||
'Concediti gentilezza.',
|
||
'La mente si calma nel corpo.',
|
||
'Il corpo ricorda la cura.',
|
||
'Fai pace con il tuo ritmo.',
|
||
'Non devi dimostrare nulla.',
|
||
'Lascia che sia abbastanza.',
|
||
'La gratitudine apre il cuore.',
|
||
'La disciplina crea libertà.',
|
||
'Piccoli gesti, grandi cambiamenti.',
|
||
'Ogni giorno ricomincia.',
|
||
'Sorridi al respiro.',
|
||
'La tua energia segue la tua attenzione.',
|
||
'Rallenta e osserva.',
|
||
'C’è forza nella dolcezza.',
|
||
'C’è bellezza nella pausa.',
|
||
'Quando dubiti, respira.',
|
||
'Lascia andare il giudizio.',
|
||
'Cerca l’equilibrio, non il controllo.',
|
||
'Senti i piedi: sei qui.',
|
||
'Senti il cuore: sei vivo.',
|
||
'Accetta, poi agisci.',
|
||
'La presenza è il vero lusso.',
|
||
'L’armonia è un allenamento.',
|
||
'Nessuna postura, solo esperienza.',
|
||
'Ogni postura è un dialogo.',
|
||
'Ascolta prima di spingere.',
|
||
'La stabilità nasce dalla morbidezza.',
|
||
'Il respiro è il tuo ancoraggio.',
|
||
'La calma è contagiosa.',
|
||
'Lascia che il corpo si apra con pazienza.',
|
||
'Scegli pensieri che ti nutrono.',
|
||
'Scegli parole che ti rispettano.',
|
||
'Scegli un ritmo sostenibile.',
|
||
'Lentamente è un modo di arrivare.',
|
||
'Fidati del processo.',
|
||
'Ogni tremore è vita.',
|
||
'Ogni difficoltà è un insegnante.',
|
||
'Non sei in ritardo.',
|
||
'Non sei indietro: sei in cammino.',
|
||
'Sii curioso, non severo.',
|
||
'La pratica non si perde mai.',
|
||
'Oggi fai un passo gentile.',
|
||
'Respira nel punto che resiste.',
|
||
'Dove c’è rigidità, porta luce.',
|
||
'Il corpo si ammorbidisce quando ti fidi.',
|
||
'La chiarezza arriva quando rallenti.',
|
||
'La mente si quieta quando senti.',
|
||
'Sii presente al 1%. È già tanto.',
|
||
'L’equilibrio è micro-aggiustamento.',
|
||
'La semplicità è potenza.',
|
||
'Lascia spazio al respiro.',
|
||
'Lascia spazio all’errore.',
|
||
'La pratica è onestà.',
|
||
'La pratica è cura.',
|
||
'Senti il tuo centro.',
|
||
'Torna al corpo, torna al presente.',
|
||
'Ogni respiro è una nuova possibilità.',
|
||
'Ogni inspirazione è un inizio.',
|
||
'Ogni espirazione è una resa.',
|
||
'Non forzare: accompagna.',
|
||
'Quando ti perdi, torna al respiro.',
|
||
'Il respiro illumina la strada.',
|
||
'Il corpo è la tua casa.',
|
||
'La pace è già qui, ascoltala.',
|
||
'Dai priorità a ciò che ti fa bene.',
|
||
'Sii fedele alla tua pratica, non all’idea.',
|
||
'La tua serenità è una scelta quotidiana.',
|
||
'Oggi scegli leggerezza.',
|
||
];
|
||
|
||
return quotes[Random().nextInt(quotes.length)];
|
||
}
|
||
|
||
Future<void> _openMaps() async {
|
||
final query = _schoolAddress.isNotEmpty
|
||
? _schoolAddress
|
||
: widget.school.name;
|
||
final uri = Uri.parse(
|
||
'https://www.google.com/maps/search/?api=1&query=${Uri.encodeComponent(query)}',
|
||
);
|
||
|
||
final ok = await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||
if (!ok && mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
const SnackBar(
|
||
content: Text('Impossibile aprire Mappe su questo dispositivo.'),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
void _goLessons() {
|
||
Navigator.pushReplacement(
|
||
context,
|
||
MaterialPageRoute(
|
||
builder: (_) => LessonsPage(
|
||
token: widget.token,
|
||
school: widget.school,
|
||
userFirstName: widget.userFirstName,
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Future<void> _logout() async {
|
||
final ok = await showDialog<bool>(
|
||
context: context,
|
||
builder: (_) => AlertDialog(
|
||
title: const Text('Conferma logout'),
|
||
content: const Text('Vuoi uscire dal tuo account?'),
|
||
actions: [
|
||
TextButton(
|
||
onPressed: () => Navigator.pop(context, false),
|
||
child: const Text('Annulla'),
|
||
),
|
||
ElevatedButton(
|
||
onPressed: () => Navigator.pop(context, true),
|
||
child: const Text('Logout'),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
|
||
if (ok != true) return;
|
||
|
||
// chiude drawer
|
||
Navigator.of(context).pop();
|
||
|
||
try {
|
||
await VanguardApi.logout(token: widget.token);
|
||
} catch (_) {}
|
||
|
||
if (!mounted) return;
|
||
|
||
Navigator.of(context).pushAndRemoveUntil(
|
||
MaterialPageRoute(builder: (_) => const LoginPage()),
|
||
(route) => false,
|
||
);
|
||
}
|
||
|
||
void _handleBottomNav(int i) {
|
||
setState(() => bottomIndex = i);
|
||
|
||
if (i == 0) return;
|
||
if (i == 1) {
|
||
_goLessons();
|
||
return;
|
||
}
|
||
if (i == 2) {
|
||
ScaffoldMessenger.of(
|
||
context,
|
||
).showSnackBar(const SnackBar(content: Text('TODO: vai ad Account')));
|
||
return;
|
||
}
|
||
if (i == 3) {
|
||
Navigator.pushReplacement(
|
||
context,
|
||
MaterialPageRoute(
|
||
builder: (_) => MeditationPage(
|
||
token: widget.token,
|
||
school: widget.school,
|
||
userFirstName: widget.userFirstName,
|
||
),
|
||
),
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
backgroundColor: kBg,
|
||
drawer: Drawer(
|
||
child: SafeArea(
|
||
child: ListView(
|
||
padding: EdgeInsets.zero,
|
||
children: [
|
||
const DrawerHeader(
|
||
child: Align(
|
||
alignment: Alignment.bottomLeft,
|
||
child: Text(
|
||
'Menu',
|
||
style: TextStyle(fontSize: 22, fontWeight: FontWeight.w900),
|
||
),
|
||
),
|
||
),
|
||
ListTile(
|
||
leading: const Icon(Icons.swap_horiz),
|
||
title: const Text('Cambia scuola'),
|
||
onTap: () {
|
||
Navigator.of(context).pop();
|
||
Navigator.pushReplacement(
|
||
context,
|
||
MaterialPageRoute(
|
||
builder: (_) => SelectSchoolPage(token: widget.token),
|
||
),
|
||
);
|
||
},
|
||
),
|
||
const Divider(height: 1),
|
||
ListTile(
|
||
leading: const Icon(Icons.logout),
|
||
title: const Text('Logout'),
|
||
onTap: _logout,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
appBar: AppBar(
|
||
backgroundColor: kBg,
|
||
elevation: 0,
|
||
centerTitle: true,
|
||
title: const Text(
|
||
'Home',
|
||
style: TextStyle(fontWeight: FontWeight.w800, fontSize: 18),
|
||
),
|
||
actions: [
|
||
Padding(
|
||
padding: const EdgeInsets.only(right: 12),
|
||
child: CircleAvatar(
|
||
radius: 16,
|
||
backgroundColor: kGreen,
|
||
child: Text(
|
||
_avatarLetter,
|
||
style: const TextStyle(
|
||
color: Colors.white,
|
||
fontWeight: FontWeight.w900,
|
||
fontSize: 12,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
bottomNavigationBar: BottomNavigationBar(
|
||
type: BottomNavigationBarType.fixed, // con 4 icone serve
|
||
backgroundColor: Colors.white,
|
||
selectedItemColor: const Color(0xFF10B981),
|
||
unselectedItemColor: Colors.black54,
|
||
currentIndex: bottomIndex,
|
||
onTap: _handleBottomNav,
|
||
items: const [
|
||
BottomNavigationBarItem(
|
||
icon: Icon(Icons.home_rounded),
|
||
label: 'Home',
|
||
),
|
||
BottomNavigationBarItem(
|
||
icon: Icon(Icons.event_note_rounded),
|
||
label: 'Lezioni',
|
||
),
|
||
BottomNavigationBarItem(
|
||
icon: Icon(Icons.person_rounded),
|
||
label: 'Account',
|
||
),
|
||
BottomNavigationBarItem(
|
||
icon: Icon(Icons.self_improvement_rounded),
|
||
label: 'Meditazione',
|
||
),
|
||
],
|
||
),
|
||
|
||
body: Padding(
|
||
padding: const EdgeInsets.fromLTRB(16, 10, 16, 16),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
// Ciao + nome
|
||
Text(
|
||
_name.isNotEmpty ? 'Ciao, $_name' : 'Ciao',
|
||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w900),
|
||
),
|
||
const SizedBox(height: 10),
|
||
|
||
// Nome scuola + indirizzo sotto
|
||
Text(
|
||
widget.school.name,
|
||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w900),
|
||
),
|
||
if (_schoolAddress.isNotEmpty) ...[
|
||
const SizedBox(height: 4),
|
||
Text(
|
||
_schoolAddress,
|
||
style: const TextStyle(
|
||
fontSize: 12,
|
||
color: Colors.black54,
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
],
|
||
const SizedBox(height: 10),
|
||
|
||
// Pulsante mappe
|
||
OutlinedButton.icon(
|
||
onPressed: _openMaps,
|
||
icon: const Icon(Icons.directions, size: 18, color: kGreen),
|
||
label: const Text(
|
||
'Apri Mappe',
|
||
style: TextStyle(fontWeight: FontWeight.w800, color: kGreen),
|
||
),
|
||
style: OutlinedButton.styleFrom(
|
||
side: const BorderSide(color: kGreen, width: 2),
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(14),
|
||
),
|
||
),
|
||
),
|
||
|
||
const SizedBox(height: 14),
|
||
|
||
// Tiles
|
||
_HomeTile(
|
||
icon: Icons.event_note_rounded,
|
||
title: 'Vedi lezioni',
|
||
subtitle: 'Prenotazioni e gestione lezioni',
|
||
onTap: _goLessons,
|
||
),
|
||
_HomeTile(
|
||
icon: Icons.shopping_bag_rounded,
|
||
title: 'Vedi ordini',
|
||
subtitle: 'Storico e stato ordini (TODO)',
|
||
onTap: () => ScaffoldMessenger.of(
|
||
context,
|
||
).showSnackBar(const SnackBar(content: Text('TODO: Ordini'))),
|
||
),
|
||
_HomeTile(
|
||
icon: Icons.person_rounded,
|
||
title: 'Account',
|
||
subtitle: 'Profilo e impostazioni (TODO)',
|
||
onTap: () => ScaffoldMessenger.of(
|
||
context,
|
||
).showSnackBar(const SnackBar(content: Text('TODO: Account'))),
|
||
),
|
||
|
||
const Spacer(),
|
||
|
||
// Logo + frase zen
|
||
Center(
|
||
child: Column(
|
||
children: [
|
||
Text(
|
||
'“$zenQuote”',
|
||
textAlign: TextAlign.center,
|
||
style: const TextStyle(
|
||
fontSize: 16,
|
||
height: 1.3,
|
||
color: Colors.black87,
|
||
fontWeight: FontWeight.w700,
|
||
),
|
||
),
|
||
const SizedBox(height: 14),
|
||
Image.asset('assets/images/yogibook_logo.png', height: 78),
|
||
const SizedBox(height: 6),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _HomeTile extends StatelessWidget {
|
||
static const Color kGreen = Color(0xFF10B981);
|
||
|
||
final IconData icon;
|
||
final String title;
|
||
final String subtitle;
|
||
final VoidCallback onTap;
|
||
|
||
const _HomeTile({
|
||
required this.icon,
|
||
required this.title,
|
||
required this.subtitle,
|
||
required this.onTap,
|
||
});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Container(
|
||
margin: const EdgeInsets.only(bottom: 12),
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.circular(18),
|
||
boxShadow: const [
|
||
BoxShadow(
|
||
blurRadius: 18,
|
||
color: Color(0x12000000),
|
||
offset: Offset(0, 10),
|
||
),
|
||
],
|
||
),
|
||
child: InkWell(
|
||
borderRadius: BorderRadius.circular(18),
|
||
onTap: onTap,
|
||
child: Padding(
|
||
padding: const EdgeInsets.fromLTRB(12, 12, 12, 12),
|
||
child: Row(
|
||
children: [
|
||
Container(
|
||
width: 44,
|
||
height: 44,
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFFE7F8F1),
|
||
borderRadius: BorderRadius.circular(14),
|
||
),
|
||
child: Icon(icon, color: kGreen),
|
||
),
|
||
const SizedBox(width: 12),
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
title,
|
||
style: const TextStyle(
|
||
fontWeight: FontWeight.w900,
|
||
fontSize: 15,
|
||
),
|
||
),
|
||
const SizedBox(height: 4),
|
||
Text(
|
||
subtitle,
|
||
style: const TextStyle(
|
||
color: Colors.black54,
|
||
fontWeight: FontWeight.w600,
|
||
fontSize: 12,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(width: 10),
|
||
Container(
|
||
width: 34,
|
||
height: 34,
|
||
decoration: const BoxDecoration(
|
||
color: kGreen,
|
||
shape: BoxShape.circle,
|
||
),
|
||
child: const Icon(Icons.chevron_right, color: Colors.white),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|