fixed bugs
This commit is contained in:
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
@@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
|
||||
@@ -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
@@ -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) ...[
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'
|
||||
'l’app 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),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -127,7 +127,11 @@ class AppDrawer extends StatelessWidget {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => OrdersDetailPage(token: token),
|
||||
builder: (_) => OrdersDetailPage(
|
||||
token: token,
|
||||
school: school,
|
||||
userFirstName: userFirstName,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user