fixed bugs

This commit is contained in:
2026-08-21 13:11:55 +02:00
parent ab36215b7a
commit 64eade33f3
16 changed files with 367 additions and 361 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

+2 -2
View File
@@ -3,7 +3,7 @@ import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:provider/provider.dart';
import 'screens/login_page.dart';
import 'screens/splash_page.dart';
import 'state/app_state.dart';
Future<void> main() async {
@@ -32,7 +32,7 @@ class MyApp extends StatelessWidget {
supportedLocales: const [Locale('it', 'IT'), Locale('en', 'US')],
locale: const Locale('it', 'IT'),
home: const LoginPage(),
home: const SplashPage(),
);
}
}
+7
View File
@@ -5,6 +5,7 @@ import 'package:image_picker/image_picker.dart';
import '../models/school.dart';
import '../services/vanguard_api.dart';
import '../widgets/yogibook_background.dart';
import '../widgets/app_bottom_nav.dart';
class AccountPage extends StatefulWidget {
final String token;
@@ -238,6 +239,12 @@ class _AccountPageState extends State<AccountPage> {
style: TextStyle(fontWeight: FontWeight.w800, fontSize: 18),
),
),
bottomNavigationBar: AppBottomNav(
currentIndex: 2,
token: widget.token,
school: widget.school,
userFirstName: widget.userFirstName,
),
body: YogibookBackground(
child: SafeArea(
top: false,
+19 -46
View File
@@ -35,8 +35,6 @@ 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
@@ -224,44 +222,6 @@ class _HomePageState extends State<HomePage> {
);
}
void _handleBottomNav(int i) {
setState(() => bottomIndex = i);
if (i == 0) return;
if (i == 1) {
_goLessons();
return;
}
if (i == 2) {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => AccountPage(
token: widget.token,
school: widget.school,
userFirstName: widget.userFirstName,
),
),
).then((_) {
if (mounted) setState(() => bottomIndex = 0);
});
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(
@@ -299,8 +259,10 @@ class _HomePageState extends State<HomePage> {
),
bottomNavigationBar: AppBottomNav(
currentIndex: bottomIndex,
onTap: _handleBottomNav,
currentIndex: 0,
token: widget.token,
school: widget.school,
userFirstName: widget.userFirstName,
),
body: YogibookBackground(
@@ -378,16 +340,27 @@ class _HomePageState extends State<HomePage> {
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => OrdersDetailPage(token: widget.token),
builder: (_) => OrdersDetailPage(
token: widget.token,
school: widget.school,
userFirstName: widget.userFirstName,
),
),
),
),
_HomeTile(
icon: Icons.person_rounded,
title: 'Account',
subtitle: 'Profilo e impostazioni (TODO)',
onTap: () => ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('TODO: Account')),
subtitle: 'Profilo e impostazioni',
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => AccountPage(
token: widget.token,
school: widget.school,
userFirstName: widget.userFirstName,
),
),
),
),
+49 -85
View File
@@ -37,8 +37,6 @@ class _LessonsPageState extends State<LessonsPage> {
LessonsSummary? summary;
List<Lesson> lessons = [];
int bottomIndex = 1;
@override
void initState() {
super.initState();
@@ -104,48 +102,6 @@ class _LessonsPageState extends State<LessonsPage> {
String get _name => (widget.userFirstName ?? '').trim();
String get _avatarLetter => _name.isNotEmpty ? _name[0].toUpperCase() : 'U';
void _handleBottomNav(int i) {
if (i == bottomIndex) return;
setState(() => bottomIndex = i);
if (i == 0) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => HomePage(
token: widget.token,
school: widget.school,
userFirstName: widget.userFirstName,
),
),
);
return;
}
if (i == 1) return; // sei già su Lezioni
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(
@@ -195,8 +151,10 @@ class _LessonsPageState extends State<LessonsPage> {
],
),
bottomNavigationBar: AppBottomNav(
currentIndex: bottomIndex,
onTap: _handleBottomNav,
currentIndex: 1,
token: widget.token,
school: widget.school,
userFirstName: widget.userFirstName,
),
body: YogibookBackground(
@@ -388,47 +346,53 @@ class _SummaryStrip extends StatelessWidget {
_SummaryItem('Perse', summary.lost, const Color(0xFFFBC7C4)),
];
return SizedBox(
height: 76,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: items.length,
separatorBuilder: (_, __) => const SizedBox(width: 8),
itemBuilder: (_, i) {
final it = items[i];
return Container(
width: 96,
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: it.color,
borderRadius: BorderRadius.circular(12),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'${it.value}',
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.w900,
// 3 colonne, spaziatura tra i box
const spacing = 8.0;
const columns = 3;
return LayoutBuilder(
builder: (context, constraints) {
final boxWidth =
(constraints.maxWidth - spacing * (columns - 1)) / columns;
return Wrap(
spacing: spacing,
runSpacing: spacing,
children: items.map((it) {
return Container(
width: boxWidth,
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 6),
decoration: BoxDecoration(
color: it.color,
borderRadius: BorderRadius.circular(12),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'${it.value}',
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.w900,
),
),
),
const SizedBox(height: 2),
Text(
it.label,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 10,
fontWeight: FontWeight.w700,
const SizedBox(height: 2),
Text(
it.label,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w700,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
);
},
),
],
),
);
}).toList(),
);
},
);
}
}
+37 -22
View File
@@ -18,6 +18,7 @@ class _LoginPageState extends State<LoginPage> {
// ---- UI state
bool loading = false;
String error = '';
bool _obscurePassword = true;
// Scuola unica (YogaSoul). Il DB non ha il concetto di scuola:
// questo oggetto serve solo a soddisfare le pagine che mostrano nome/indirizzo.
@@ -61,7 +62,21 @@ class _LoginPageState extends State<LoginPage> {
),
);
} catch (e) {
setState(() => error = 'Errore login: $e');
final msg = e.toString();
String friendly;
if (msg.contains('401') || msg.contains('403')) {
friendly = 'Email o password non corretti.';
} else if (msg.contains('SocketException') ||
msg.contains('Failed host lookup') ||
msg.contains('Connection')) {
friendly = 'Nessuna connessione. Controlla la rete e riprova.';
} else if (msg.contains('timed out') ||
msg.contains('TimeoutException')) {
friendly = 'Il server non risponde. Riprova tra poco.';
} else {
friendly = 'Accesso non riuscito. Riprova.';
}
setState(() => error = friendly);
} finally {
if (mounted) setState(() => loading = false);
}
@@ -175,44 +190,44 @@ class _LoginPageState extends State<LoginPage> {
controller: emailController,
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
decoration: InputDecoration(
decoration: const InputDecoration(
labelText: 'Email / Username',
border: const OutlineInputBorder(),
enabled: !loading,
border: OutlineInputBorder(),
),
),
const SizedBox(height: 12),
TextField(
controller: passwordController,
obscureText: true,
obscureText: _obscurePassword,
textInputAction: TextInputAction.done,
onSubmitted: (_) => loading ? null : doLogin(),
decoration: InputDecoration(
labelText: 'Password',
border: const OutlineInputBorder(),
enabled: !loading,
suffixIcon: IconButton(
icon: Icon(
_obscurePassword
? Icons.visibility
: Icons.visibility_off,
),
onPressed: loading
? null
: () => setState(
() => _obscurePassword = !_obscurePassword,
),
),
),
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
onPressed: loading ? null : forgotPassword,
child: const Text('Recupero password'),
),
TextButton(
onPressed: () =>
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Registrazione: dopo.'),
),
),
child: const Text('Registrati'),
),
],
Align(
alignment: Alignment.centerRight,
child: TextButton(
onPressed: loading ? null : forgotPassword,
child: const Text('Recupero password'),
),
),
if (error.isNotEmpty) ...[
+4 -54
View File
@@ -50,9 +50,6 @@ class _MedicalCertificatesPageState extends State<MedicalCertificatesPage> {
DateTime? expiryDate;
bool uploading = false;
// ✅ questa pagina è "sezione account"
int bottomIndex = 2;
@override
void initState() {
super.initState();
@@ -286,54 +283,6 @@ class _MedicalCertificatesPageState extends State<MedicalCertificatesPage> {
bool _isPdfPath(String p) => p.toLowerCase().endsWith('.pdf');
Future<void> _goTo(Widget page) async {
if (!mounted) return;
Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => page));
}
void _handleBottomNav(int i) {
if (i == bottomIndex) return;
setState(() => bottomIndex = i);
if (i == 0) {
_goTo(
HomePage(
token: widget.token,
school: widget.school,
userFirstName: widget.userFirstName,
),
);
return;
}
if (i == 1) {
_goTo(
LessonsPage(
token: widget.token,
school: widget.school,
userFirstName: widget.userFirstName,
),
);
return;
}
if (i == 2) {
// siamo già in "area account", non facciamo nulla (o TODO)
return;
}
if (i == 3) {
_goTo(
MeditationPage(
token: widget.token,
school: widget.school,
userFirstName: widget.userFirstName,
),
);
return;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -359,10 +308,11 @@ class _MedicalCertificatesPageState extends State<MedicalCertificatesPage> {
],
),
// ✅ Bottom nav standard (come MeditationPage)
bottomNavigationBar: AppBottomNav(
currentIndex: bottomIndex,
onTap: _handleBottomNav,
currentIndex: 2,
token: widget.token,
school: widget.school,
userFirstName: widget.userFirstName,
),
body: YogibookBackground(
+9 -63
View File
@@ -64,8 +64,6 @@ class _MeditationPageState extends State<MeditationPage>
late final AnimationController _pulseCtrl;
late final AnimationController _squareCtrl;
int bottomIndex = 3; // Meditazione
@override
void initState() {
super.initState();
@@ -256,42 +254,6 @@ class _MeditationPageState extends State<MeditationPage>
Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => page));
}
void _handleBottomNav(int i) {
if (i == bottomIndex) return;
setState(() => bottomIndex = i);
if (i == 0) {
_goTo(
HomePage(
token: widget.token,
school: widget.school,
userFirstName: widget.userFirstName,
),
);
return;
}
if (i == 1) {
_goTo(
LessonsPage(
token: widget.token,
school: widget.school,
userFirstName: widget.userFirstName,
),
);
return;
}
if (i == 2) {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('TODO: vai ad Account')));
return;
}
// i == 3 => già qui
}
@override
Widget build(BuildContext context) {
final bubbleScale = Tween<double>(
@@ -340,31 +302,15 @@ class _MeditationPageState extends State<MeditationPage>
),
],
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
backgroundColor: Colors.white,
selectedItemColor: kGreen,
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',
),
],
bottomNavigationBar: AppBottomNav(
currentIndex: 3,
token: widget.token,
school: widget.school,
userFirstName: widget.userFirstName,
onBeforeLeave: () async {
_pause();
await _stopMusic();
},
),
body: YogibookBackground(
child: SafeArea(
+16 -1
View File
@@ -1,15 +1,24 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../models/lesson.dart';
import '../models/school.dart';
import '../services/lessons_api.dart';
import '../widgets/yogibook_background.dart';
import '../widgets/app_bottom_nav.dart';
/// Schermata "I miei ordini": lista ordini con conteggi.
/// Toccando un ordine si apre il dettaglio (box + lezioni).
class OrdersDetailPage extends StatefulWidget {
final String token;
final School school;
final String? userFirstName;
const OrdersDetailPage({super.key, required this.token});
const OrdersDetailPage({
super.key,
required this.token,
required this.school,
this.userFirstName,
});
@override
State<OrdersDetailPage> createState() => _OrdersDetailPageState();
@@ -63,6 +72,12 @@ class _OrdersDetailPageState extends State<OrdersDetailPage> {
style: TextStyle(fontWeight: FontWeight.w800, fontSize: 18),
),
),
bottomNavigationBar: AppBottomNav(
currentIndex: 1,
token: widget.token,
school: widget.school,
userFirstName: widget.userFirstName,
),
body: YogibookBackground(
child: SafeArea(
top: false,
+150
View File
@@ -0,0 +1,150 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'login_page.dart';
class SplashPage extends StatefulWidget {
const SplashPage({super.key});
@override
State<SplashPage> createState() => _SplashPageState();
}
class _SplashPageState extends State<SplashPage> {
// rosso bordeaux ripreso dal cuore del logo YogaSoul
static const Color kRed = Color(0xFFB01E28);
Timer? _timer;
@override
void initState() {
super.initState();
// dopo ~2.5s vai al login
_timer = Timer(const Duration(milliseconds: 5000), _goToLogin);
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
void _goToLogin() {
if (!mounted) return;
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => const LoginPage()),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
// tocca lo schermo per saltare subito al login
body: GestureDetector(
onTap: _goToLogin,
child: Stack(
fit: StackFit.expand,
children: [
// ---- Foto di sfondo full-screen
Image.asset('assets/images/splash_bg.png', fit: BoxFit.cover),
// ---- Contenuto (logo + testo) in alto
SafeArea(
child: Padding(
padding: const EdgeInsets.fromLTRB(24, 10, 24, 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(height: 8),
// Logo YogaSoul
Image.asset(
'assets/images/yogasoul_logo.png',
height: 90,
fit: BoxFit.contain,
),
const SizedBox(height: 26),
// ---- Blocco testo su velo bianco morbido per leggibilità
Container(
padding: const EdgeInsets.symmetric(
horizontal: 18,
vertical: 16,
),
decoration: BoxDecoration(
// velo bianco semitrasparente: stacca il testo dal cielo
color: Colors.white.withOpacity(0.42),
borderRadius: BorderRadius.circular(20),
),
child: Column(
children: [
// Titolo riga 1 (scuro)
const Text(
'Il tuo spazio.',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 30,
height: 1.1,
fontWeight: FontWeight.w900,
color: Color(0xFF1F1F1F),
),
),
// Titolo riga 2 (rosso bordeaux)
const Text(
'La tua energia.',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 30,
height: 1.1,
fontWeight: FontWeight.w900,
color: kRed,
),
),
const SizedBox(height: 14),
// Sottotitolo
const Text(
'Ti diamo il benvenuto in YogiBook,\n'
'lapp della scuola YogaSoul — Seregno.\n'
'Ti accompagna nelle tue lezioni e nei tuoi pacchetti.',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
height: 1.4,
fontWeight: FontWeight.w600,
color: Color(0xFF2A2A2A),
),
),
],
),
),
],
),
),
),
// ---- Indicatore caricamento in basso
const Positioned(
left: 0,
right: 0,
bottom: 34,
child: Center(
child: SizedBox(
width: 26,
height: 26,
child: CircularProgressIndicator(
strokeWidth: 2.4,
valueColor: AlwaysStoppedAnimation(Colors.white),
),
),
),
),
],
),
),
);
}
}
-84
View File
@@ -1,84 +0,0 @@
import 'package:flutter/material.dart';
import 'main.dart'; // per VanguardApi
class SelectSchoolPage extends StatefulWidget {
final String token;
const SelectSchoolPage({super.key, required this.token});
@override
State<SelectSchoolPage> createState() => _SelectSchoolPageState();
}
class _SelectSchoolPageState extends State<SelectSchoolPage> {
bool loading = true;
String error = '';
List schools = [];
@override
void initState() {
super.initState();
_loadSchools();
}
Future<void> _loadSchools() async {
try {
final data = await VanguardApi.getUserSchools(token: widget.token);
if (data['success'] != true) {
throw Exception(data['message'] ?? 'Errore sconosciuto');
}
final List list = data['schools'] ?? [];
// Auto select
if (data['auto_select'] == true && list.length == 1) {
_enterSchool(list.first);
return;
}
setState(() {
schools = list;
loading = false;
});
} catch (e) {
setState(() {
error = e.toString();
loading = false;
});
}
}
void _enterSchool(Map school) {
// per ora debug
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => SuccessPage(token: widget.token)),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Scegli la scuola')),
body: loading
? const Center(child: CircularProgressIndicator())
: error.isNotEmpty
? Center(child: Text(error))
: ListView.builder(
itemCount: schools.length,
itemBuilder: (context, i) {
final s = schools[i];
return ListTile(
leading: s['logo'] != null
? Image.network(s['logo'], width: 40)
: const Icon(Icons.school),
title: Text(s['name']),
subtitle: Text(s['address_full'] ?? ''),
onTap: () => _enterSchool(s),
);
},
),
);
}
}
+67 -3
View File
@@ -1,15 +1,79 @@
import 'package:flutter/material.dart';
import '../models/school.dart';
import '../screens/home_page.dart';
import '../screens/lessons_page.dart';
import '../screens/account_page.dart';
import '../screens/meditation_page.dart';
/// Bottom navigation centralizzata.
/// Gestisce da sé la navigazione tra le 4 tab principali.
/// Ogni pagina passa solo il proprio [currentIndex] e i dati utente.
///
/// [onBeforeLeave] è opzionale: le pagine che devono fare pulizia prima di
/// uscire (es. Meditazione che ferma musica/timer) lo passano.
class AppBottomNav extends StatelessWidget {
final int currentIndex;
final ValueChanged<int> onTap;
final String token;
final School school;
final String? userFirstName;
final Future<void> Function()? onBeforeLeave;
const AppBottomNav({
super.key,
required this.currentIndex,
required this.onTap,
required this.token,
required this.school,
this.userFirstName,
this.onBeforeLeave,
});
Future<void> _go(BuildContext context, int i) async {
if (i == currentIndex) return;
// pulizia eventuale (musica/timer) prima di lasciare la pagina
if (onBeforeLeave != null) {
await onBeforeLeave!();
}
if (!context.mounted) return;
Widget page;
switch (i) {
case 0:
page = HomePage(
token: token,
school: school,
userFirstName: userFirstName,
);
break;
case 1:
page = LessonsPage(
token: token,
school: school,
userFirstName: userFirstName,
);
break;
case 2:
page = AccountPage(
token: token,
school: school,
userFirstName: userFirstName,
);
break;
case 3:
page = MeditationPage(
token: token,
school: school,
userFirstName: userFirstName,
);
break;
default:
return;
}
Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => page));
}
@override
Widget build(BuildContext context) {
return BottomNavigationBar(
@@ -18,7 +82,7 @@ class AppBottomNav extends StatelessWidget {
selectedItemColor: const Color(0xFF10B981),
unselectedItemColor: Colors.black54,
currentIndex: currentIndex,
onTap: onTap,
onTap: (i) => _go(context, i),
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home_rounded), label: 'Home'),
BottomNavigationBarItem(
+5 -1
View File
@@ -127,7 +127,11 @@ class AppDrawer extends StatelessWidget {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => OrdersDetailPage(token: token),
builder: (_) => OrdersDetailPage(
token: token,
school: school,
userFirstName: userFirstName,
),
),
);
},
+2
View File
@@ -67,6 +67,8 @@ flutter:
uses-material-design: true
assets:
- assets/images/yogibook_logo.png
- assets/images/splash_bg.png
- assets/images/yogasoul_logo.png
- assets/audio/meditation.mp3
# To add assets to your application, add an assets section, like this: