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:intl/date_symbol_data_local.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'screens/login_page.dart';
|
import 'screens/splash_page.dart';
|
||||||
import 'state/app_state.dart';
|
import 'state/app_state.dart';
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
@@ -32,7 +32,7 @@ class MyApp extends StatelessWidget {
|
|||||||
supportedLocales: const [Locale('it', 'IT'), Locale('en', 'US')],
|
supportedLocales: const [Locale('it', 'IT'), Locale('en', 'US')],
|
||||||
locale: const Locale('it', 'IT'),
|
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 '../models/school.dart';
|
||||||
import '../services/vanguard_api.dart';
|
import '../services/vanguard_api.dart';
|
||||||
import '../widgets/yogibook_background.dart';
|
import '../widgets/yogibook_background.dart';
|
||||||
|
import '../widgets/app_bottom_nav.dart';
|
||||||
|
|
||||||
class AccountPage extends StatefulWidget {
|
class AccountPage extends StatefulWidget {
|
||||||
final String token;
|
final String token;
|
||||||
@@ -238,6 +239,12 @@ class _AccountPageState extends State<AccountPage> {
|
|||||||
style: TextStyle(fontWeight: FontWeight.w800, fontSize: 18),
|
style: TextStyle(fontWeight: FontWeight.w800, fontSize: 18),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
bottomNavigationBar: AppBottomNav(
|
||||||
|
currentIndex: 2,
|
||||||
|
token: widget.token,
|
||||||
|
school: widget.school,
|
||||||
|
userFirstName: widget.userFirstName,
|
||||||
|
),
|
||||||
body: YogibookBackground(
|
body: YogibookBackground(
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
top: false,
|
top: false,
|
||||||
|
|||||||
+19
-46
@@ -35,8 +35,6 @@ class _HomePageState extends State<HomePage> {
|
|||||||
static const Color kBg = Color(0xFFF6F6FB);
|
static const Color kBg = Color(0xFFF6F6FB);
|
||||||
static const Color kGreen = Color(0xFF10B981);
|
static const Color kGreen = Color(0xFF10B981);
|
||||||
|
|
||||||
int bottomIndex = 0; // Home
|
|
||||||
|
|
||||||
late final String zenQuote;
|
late final String zenQuote;
|
||||||
|
|
||||||
@override
|
@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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -299,8 +259,10 @@ class _HomePageState extends State<HomePage> {
|
|||||||
),
|
),
|
||||||
|
|
||||||
bottomNavigationBar: AppBottomNav(
|
bottomNavigationBar: AppBottomNav(
|
||||||
currentIndex: bottomIndex,
|
currentIndex: 0,
|
||||||
onTap: _handleBottomNav,
|
token: widget.token,
|
||||||
|
school: widget.school,
|
||||||
|
userFirstName: widget.userFirstName,
|
||||||
),
|
),
|
||||||
|
|
||||||
body: YogibookBackground(
|
body: YogibookBackground(
|
||||||
@@ -378,16 +340,27 @@ class _HomePageState extends State<HomePage> {
|
|||||||
onTap: () => Navigator.push(
|
onTap: () => Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => OrdersDetailPage(token: widget.token),
|
builder: (_) => OrdersDetailPage(
|
||||||
|
token: widget.token,
|
||||||
|
school: widget.school,
|
||||||
|
userFirstName: widget.userFirstName,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_HomeTile(
|
_HomeTile(
|
||||||
icon: Icons.person_rounded,
|
icon: Icons.person_rounded,
|
||||||
title: 'Account',
|
title: 'Account',
|
||||||
subtitle: 'Profilo e impostazioni (TODO)',
|
subtitle: 'Profilo e impostazioni',
|
||||||
onTap: () => ScaffoldMessenger.of(context).showSnackBar(
|
onTap: () => Navigator.push(
|
||||||
const SnackBar(content: Text('TODO: Account')),
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => AccountPage(
|
||||||
|
token: widget.token,
|
||||||
|
school: widget.school,
|
||||||
|
userFirstName: widget.userFirstName,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|||||||
@@ -37,8 +37,6 @@ class _LessonsPageState extends State<LessonsPage> {
|
|||||||
LessonsSummary? summary;
|
LessonsSummary? summary;
|
||||||
List<Lesson> lessons = [];
|
List<Lesson> lessons = [];
|
||||||
|
|
||||||
int bottomIndex = 1;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -104,48 +102,6 @@ class _LessonsPageState extends State<LessonsPage> {
|
|||||||
String get _name => (widget.userFirstName ?? '').trim();
|
String get _name => (widget.userFirstName ?? '').trim();
|
||||||
String get _avatarLetter => _name.isNotEmpty ? _name[0].toUpperCase() : 'U';
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -195,8 +151,10 @@ class _LessonsPageState extends State<LessonsPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
bottomNavigationBar: AppBottomNav(
|
bottomNavigationBar: AppBottomNav(
|
||||||
currentIndex: bottomIndex,
|
currentIndex: 1,
|
||||||
onTap: _handleBottomNav,
|
token: widget.token,
|
||||||
|
school: widget.school,
|
||||||
|
userFirstName: widget.userFirstName,
|
||||||
),
|
),
|
||||||
|
|
||||||
body: YogibookBackground(
|
body: YogibookBackground(
|
||||||
@@ -388,47 +346,53 @@ class _SummaryStrip extends StatelessWidget {
|
|||||||
_SummaryItem('Perse', summary.lost, const Color(0xFFFBC7C4)),
|
_SummaryItem('Perse', summary.lost, const Color(0xFFFBC7C4)),
|
||||||
];
|
];
|
||||||
|
|
||||||
return SizedBox(
|
// 3 colonne, spaziatura tra i box
|
||||||
height: 76,
|
const spacing = 8.0;
|
||||||
child: ListView.separated(
|
const columns = 3;
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
itemCount: items.length,
|
return LayoutBuilder(
|
||||||
separatorBuilder: (_, __) => const SizedBox(width: 8),
|
builder: (context, constraints) {
|
||||||
itemBuilder: (_, i) {
|
final boxWidth =
|
||||||
final it = items[i];
|
(constraints.maxWidth - spacing * (columns - 1)) / columns;
|
||||||
return Container(
|
|
||||||
width: 96,
|
return Wrap(
|
||||||
padding: const EdgeInsets.all(8),
|
spacing: spacing,
|
||||||
decoration: BoxDecoration(
|
runSpacing: spacing,
|
||||||
color: it.color,
|
children: items.map((it) {
|
||||||
borderRadius: BorderRadius.circular(12),
|
return Container(
|
||||||
),
|
width: boxWidth,
|
||||||
child: Column(
|
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 6),
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
decoration: BoxDecoration(
|
||||||
children: [
|
color: it.color,
|
||||||
Text(
|
borderRadius: BorderRadius.circular(12),
|
||||||
'${it.value}',
|
),
|
||||||
style: const TextStyle(
|
child: Column(
|
||||||
fontSize: 22,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
fontWeight: FontWeight.w900,
|
children: [
|
||||||
|
Text(
|
||||||
|
'${it.value}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: FontWeight.w900,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 2),
|
||||||
const SizedBox(height: 2),
|
Text(
|
||||||
Text(
|
it.label,
|
||||||
it.label,
|
textAlign: TextAlign.center,
|
||||||
textAlign: TextAlign.center,
|
style: const TextStyle(
|
||||||
style: const TextStyle(
|
fontSize: 11,
|
||||||
fontSize: 10,
|
fontWeight: FontWeight.w700,
|
||||||
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
|
// ---- UI state
|
||||||
bool loading = false;
|
bool loading = false;
|
||||||
String error = '';
|
String error = '';
|
||||||
|
bool _obscurePassword = true;
|
||||||
|
|
||||||
// Scuola unica (YogaSoul). Il DB non ha il concetto di scuola:
|
// Scuola unica (YogaSoul). Il DB non ha il concetto di scuola:
|
||||||
// questo oggetto serve solo a soddisfare le pagine che mostrano nome/indirizzo.
|
// questo oggetto serve solo a soddisfare le pagine che mostrano nome/indirizzo.
|
||||||
@@ -61,7 +62,21 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} 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 {
|
} finally {
|
||||||
if (mounted) setState(() => loading = false);
|
if (mounted) setState(() => loading = false);
|
||||||
}
|
}
|
||||||
@@ -175,44 +190,44 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
controller: emailController,
|
controller: emailController,
|
||||||
keyboardType: TextInputType.emailAddress,
|
keyboardType: TextInputType.emailAddress,
|
||||||
textInputAction: TextInputAction.next,
|
textInputAction: TextInputAction.next,
|
||||||
decoration: InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: 'Email / Username',
|
labelText: 'Email / Username',
|
||||||
border: const OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
enabled: !loading,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
TextField(
|
TextField(
|
||||||
controller: passwordController,
|
controller: passwordController,
|
||||||
obscureText: true,
|
obscureText: _obscurePassword,
|
||||||
textInputAction: TextInputAction.done,
|
textInputAction: TextInputAction.done,
|
||||||
onSubmitted: (_) => loading ? null : doLogin(),
|
onSubmitted: (_) => loading ? null : doLogin(),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Password',
|
labelText: 'Password',
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
enabled: !loading,
|
enabled: !loading,
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
_obscurePassword
|
||||||
|
? Icons.visibility
|
||||||
|
: Icons.visibility_off,
|
||||||
|
),
|
||||||
|
onPressed: loading
|
||||||
|
? null
|
||||||
|
: () => setState(
|
||||||
|
() => _obscurePassword = !_obscurePassword,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Row(
|
Align(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
alignment: Alignment.centerRight,
|
||||||
children: [
|
child: TextButton(
|
||||||
TextButton(
|
onPressed: loading ? null : forgotPassword,
|
||||||
onPressed: loading ? null : forgotPassword,
|
child: const Text('Recupero password'),
|
||||||
child: const Text('Recupero password'),
|
),
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () =>
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(
|
|
||||||
content: Text('Registrazione: dopo.'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: const Text('Registrati'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
|
||||||
if (error.isNotEmpty) ...[
|
if (error.isNotEmpty) ...[
|
||||||
|
|||||||
@@ -50,9 +50,6 @@ class _MedicalCertificatesPageState extends State<MedicalCertificatesPage> {
|
|||||||
DateTime? expiryDate;
|
DateTime? expiryDate;
|
||||||
bool uploading = false;
|
bool uploading = false;
|
||||||
|
|
||||||
// ✅ questa pagina è "sezione account"
|
|
||||||
int bottomIndex = 2;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -286,54 +283,6 @@ class _MedicalCertificatesPageState extends State<MedicalCertificatesPage> {
|
|||||||
|
|
||||||
bool _isPdfPath(String p) => p.toLowerCase().endsWith('.pdf');
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -359,10 +308,11 @@ class _MedicalCertificatesPageState extends State<MedicalCertificatesPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
// ✅ Bottom nav standard (come MeditationPage)
|
|
||||||
bottomNavigationBar: AppBottomNav(
|
bottomNavigationBar: AppBottomNav(
|
||||||
currentIndex: bottomIndex,
|
currentIndex: 2,
|
||||||
onTap: _handleBottomNav,
|
token: widget.token,
|
||||||
|
school: widget.school,
|
||||||
|
userFirstName: widget.userFirstName,
|
||||||
),
|
),
|
||||||
|
|
||||||
body: YogibookBackground(
|
body: YogibookBackground(
|
||||||
|
|||||||
@@ -64,8 +64,6 @@ class _MeditationPageState extends State<MeditationPage>
|
|||||||
late final AnimationController _pulseCtrl;
|
late final AnimationController _pulseCtrl;
|
||||||
late final AnimationController _squareCtrl;
|
late final AnimationController _squareCtrl;
|
||||||
|
|
||||||
int bottomIndex = 3; // Meditazione
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -256,42 +254,6 @@ class _MeditationPageState extends State<MeditationPage>
|
|||||||
Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => page));
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final bubbleScale = Tween<double>(
|
final bubbleScale = Tween<double>(
|
||||||
@@ -340,31 +302,15 @@ class _MeditationPageState extends State<MeditationPage>
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
bottomNavigationBar: BottomNavigationBar(
|
bottomNavigationBar: AppBottomNav(
|
||||||
type: BottomNavigationBarType.fixed,
|
currentIndex: 3,
|
||||||
backgroundColor: Colors.white,
|
token: widget.token,
|
||||||
selectedItemColor: kGreen,
|
school: widget.school,
|
||||||
unselectedItemColor: Colors.black54,
|
userFirstName: widget.userFirstName,
|
||||||
currentIndex: bottomIndex,
|
onBeforeLeave: () async {
|
||||||
onTap: _handleBottomNav,
|
_pause();
|
||||||
items: const [
|
await _stopMusic();
|
||||||
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: YogibookBackground(
|
body: YogibookBackground(
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
|
|||||||
@@ -1,15 +1,24 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import '../models/lesson.dart';
|
import '../models/lesson.dart';
|
||||||
|
import '../models/school.dart';
|
||||||
import '../services/lessons_api.dart';
|
import '../services/lessons_api.dart';
|
||||||
import '../widgets/yogibook_background.dart';
|
import '../widgets/yogibook_background.dart';
|
||||||
|
import '../widgets/app_bottom_nav.dart';
|
||||||
|
|
||||||
/// Schermata "I miei ordini": lista ordini con conteggi.
|
/// Schermata "I miei ordini": lista ordini con conteggi.
|
||||||
/// Toccando un ordine si apre il dettaglio (box + lezioni).
|
/// Toccando un ordine si apre il dettaglio (box + lezioni).
|
||||||
class OrdersDetailPage extends StatefulWidget {
|
class OrdersDetailPage extends StatefulWidget {
|
||||||
final String token;
|
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
|
@override
|
||||||
State<OrdersDetailPage> createState() => _OrdersDetailPageState();
|
State<OrdersDetailPage> createState() => _OrdersDetailPageState();
|
||||||
@@ -63,6 +72,12 @@ class _OrdersDetailPageState extends State<OrdersDetailPage> {
|
|||||||
style: TextStyle(fontWeight: FontWeight.w800, fontSize: 18),
|
style: TextStyle(fontWeight: FontWeight.w800, fontSize: 18),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
bottomNavigationBar: AppBottomNav(
|
||||||
|
currentIndex: 1,
|
||||||
|
token: widget.token,
|
||||||
|
school: widget.school,
|
||||||
|
userFirstName: widget.userFirstName,
|
||||||
|
),
|
||||||
body: YogibookBackground(
|
body: YogibookBackground(
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
top: false,
|
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 '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 {
|
class AppBottomNav extends StatelessWidget {
|
||||||
final int currentIndex;
|
final int currentIndex;
|
||||||
final ValueChanged<int> onTap;
|
final String token;
|
||||||
|
final School school;
|
||||||
|
final String? userFirstName;
|
||||||
|
final Future<void> Function()? onBeforeLeave;
|
||||||
|
|
||||||
const AppBottomNav({
|
const AppBottomNav({
|
||||||
super.key,
|
super.key,
|
||||||
required this.currentIndex,
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BottomNavigationBar(
|
return BottomNavigationBar(
|
||||||
@@ -18,7 +82,7 @@ class AppBottomNav extends StatelessWidget {
|
|||||||
selectedItemColor: const Color(0xFF10B981),
|
selectedItemColor: const Color(0xFF10B981),
|
||||||
unselectedItemColor: Colors.black54,
|
unselectedItemColor: Colors.black54,
|
||||||
currentIndex: currentIndex,
|
currentIndex: currentIndex,
|
||||||
onTap: onTap,
|
onTap: (i) => _go(context, i),
|
||||||
items: const [
|
items: const [
|
||||||
BottomNavigationBarItem(icon: Icon(Icons.home_rounded), label: 'Home'),
|
BottomNavigationBarItem(icon: Icon(Icons.home_rounded), label: 'Home'),
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
|
|||||||
@@ -127,7 +127,11 @@ class AppDrawer extends StatelessWidget {
|
|||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => OrdersDetailPage(token: token),
|
builder: (_) => OrdersDetailPage(
|
||||||
|
token: token,
|
||||||
|
school: school,
|
||||||
|
userFirstName: userFirstName,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ flutter:
|
|||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
assets:
|
assets:
|
||||||
- assets/images/yogibook_logo.png
|
- assets/images/yogibook_logo.png
|
||||||
|
- assets/images/splash_bg.png
|
||||||
|
- assets/images/yogasoul_logo.png
|
||||||
- assets/audio/meditation.mp3
|
- assets/audio/meditation.mp3
|
||||||
|
|
||||||
# To add assets to your application, add an assets section, like this:
|
# To add assets to your application, add an assets section, like this:
|
||||||
|
|||||||
Reference in New Issue
Block a user