first commit
This commit is contained in:
@@ -0,0 +1,507 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../models/school.dart';
|
||||
import '../services/vanguard_api.dart';
|
||||
import 'lessons_page.dart';
|
||||
import 'home_page.dart';
|
||||
import 'meditation_page.dart';
|
||||
import 'login_page.dart';
|
||||
|
||||
class SelectSchoolPage extends StatefulWidget {
|
||||
final String token;
|
||||
const SelectSchoolPage({super.key, required this.token});
|
||||
|
||||
@override
|
||||
State<SelectSchoolPage> createState() => _SelectSchoolPageState();
|
||||
}
|
||||
|
||||
class _SelectSchoolPageState extends State<SelectSchoolPage> {
|
||||
static const Color kBg = Color(0xFFF6F6FB);
|
||||
static const Color kGreen = Color(0xFF10B981);
|
||||
|
||||
bool loading = true;
|
||||
String error = '';
|
||||
String? firstName;
|
||||
List<School> schools = [];
|
||||
|
||||
int bottomIndex = 1; // 0=Home, 1=Lezioni, 2=Account (qui siamo in "Lezioni")
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_load();
|
||||
}
|
||||
|
||||
Future<void> _load() async {
|
||||
setState(() {
|
||||
loading = true;
|
||||
error = '';
|
||||
});
|
||||
|
||||
try {
|
||||
final data = await VanguardApi.getUserSchools(token: widget.token);
|
||||
final user = (data['user'] as Map<String, dynamic>? ?? {});
|
||||
firstName = user['first_name']?.toString();
|
||||
|
||||
final list = (data['schools'] as List<dynamic>? ?? [])
|
||||
.map((e) => School.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
setState(() => schools = list);
|
||||
|
||||
// Auto-select if API says so
|
||||
final autoSelect = data['auto_select'] == true;
|
||||
final selectedId = data['selected_school_id'];
|
||||
if (autoSelect && selectedId != null && schools.isNotEmpty) {
|
||||
final id = (selectedId as num).toInt();
|
||||
final s = schools.firstWhere((x) => x.id == id);
|
||||
_enterSchool(s);
|
||||
}
|
||||
} catch (e) {
|
||||
setState(() => error = 'Errore: $e');
|
||||
} finally {
|
||||
setState(() => loading = false);
|
||||
}
|
||||
}
|
||||
|
||||
void _enterSchool(School s) {
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) =>
|
||||
HomePage(token: widget.token, school: s, userFirstName: firstName),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String get _name => (firstName ?? '').trim();
|
||||
String get _avatarLetter => _name.isNotEmpty ? _name[0].toUpperCase() : 'U';
|
||||
|
||||
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 il drawer se aperto
|
||||
Navigator.of(context).pop();
|
||||
|
||||
// chiama API logout (se fallisce usciamo lo stesso)
|
||||
try {
|
||||
await VanguardApi.logout(token: widget.token);
|
||||
} catch (_) {}
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
// torna a Login e cancella tutta la stack
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(builder: (_) => const LoginPage()),
|
||||
(route) => false,
|
||||
);
|
||||
}
|
||||
|
||||
void _handleBottomNav(int i) {
|
||||
setState(() => bottomIndex = i);
|
||||
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('Prima seleziona la scuola')));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
drawer: _AppDrawer(token: widget.token, onLogout: _logout),
|
||||
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
title: const Text(
|
||||
'Cambia scuola',
|
||||
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: Stack(
|
||||
children: [
|
||||
// --- SFONDO (gradient)
|
||||
Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
Color(0xFFF4F6FA),
|
||||
Color(0xFFEFF7F3),
|
||||
Color(0xFFF4F6FA),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// --- BOLLE decorative
|
||||
const _BgBlob(
|
||||
color: Color(0x3310B981),
|
||||
size: 280,
|
||||
top: -70,
|
||||
left: -90,
|
||||
),
|
||||
const _BgBlob(
|
||||
color: Color(0x22F59E0B),
|
||||
size: 230,
|
||||
bottom: 80,
|
||||
right: -70,
|
||||
),
|
||||
const _BgBlob(
|
||||
color: Color(0x227C3AED),
|
||||
size: 190,
|
||||
bottom: -55,
|
||||
left: 35,
|
||||
),
|
||||
|
||||
// --- CONTENUTO della pagina (quello che avevi prima)
|
||||
SafeArea(
|
||||
top: false,
|
||||
child: loading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: error.isNotEmpty
|
||||
? _ErrorBox(message: error, onRetry: _load)
|
||||
: _SchoolsList(
|
||||
firstName: firstName,
|
||||
schools: schools,
|
||||
onSelect: _enterSchool,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AppDrawer extends StatelessWidget {
|
||||
final String token;
|
||||
final VoidCallback onLogout;
|
||||
|
||||
const _AppDrawer({required this.token, required this.onLogout});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return 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(), // sei già qui
|
||||
),
|
||||
const Divider(height: 1),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.logout),
|
||||
title: const Text('Logout'),
|
||||
onTap: onLogout,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SchoolsList extends StatelessWidget {
|
||||
static const Color kGreen = Color(0xFF10B981);
|
||||
|
||||
final String? firstName;
|
||||
final List<School> schools;
|
||||
final void Function(School) onSelect;
|
||||
|
||||
const _SchoolsList({
|
||||
required this.firstName,
|
||||
required this.schools,
|
||||
required this.onSelect,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final name = (firstName ?? '').trim();
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// header compatto (stesso stile “soft” delle lezioni)
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
name.isNotEmpty
|
||||
? 'Seleziona una scuola, $name'
|
||||
: 'Seleziona una scuola',
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Color(0xFF2B2B2B),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
const Text(
|
||||
'Scegli la scuola di yoga.',
|
||||
style: TextStyle(
|
||||
color: Colors.black45,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||
itemCount: schools.length,
|
||||
itemBuilder: (_, i) {
|
||||
final s = schools[i];
|
||||
return _SchoolCard(school: s, onTap: () => onSelect(s));
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Image.asset('assets/images/yogibook_logo.png', height: 56),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SchoolCard extends StatelessWidget {
|
||||
static const Color kGreen = Color(0xFF10B981);
|
||||
|
||||
final School school;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _SchoolCard({required this.school, required this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final address = (school.addressFull ?? '').trim();
|
||||
|
||||
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: const Icon(Icons.school, color: kGreen),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
school.name,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 15,
|
||||
color: Color(0xFF1F1F1F),
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
if (address.isNotEmpty) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
address,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.black54,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ErrorBox extends StatelessWidget {
|
||||
final String message;
|
||||
final VoidCallback onRetry;
|
||||
const _ErrorBox({required this.message, required this.onRetry});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(18),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.error_outline, size: 58, color: Colors.redAccent),
|
||||
const SizedBox(height: 10),
|
||||
Text(message, textAlign: TextAlign.center),
|
||||
const SizedBox(height: 12),
|
||||
ElevatedButton(onPressed: onRetry, child: const Text('Riprova')),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _BgBlob extends StatelessWidget {
|
||||
final Color color;
|
||||
final double size;
|
||||
final double? top;
|
||||
final double? left;
|
||||
final double? right;
|
||||
final double? bottom;
|
||||
|
||||
const _BgBlob({
|
||||
required this.color,
|
||||
required this.size,
|
||||
this.top,
|
||||
this.left,
|
||||
this.right,
|
||||
this.bottom,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Positioned(
|
||||
top: top,
|
||||
left: left,
|
||||
right: right,
|
||||
bottom: bottom,
|
||||
child: IgnorePointer(
|
||||
child: Container(
|
||||
width: size,
|
||||
height: size,
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
blurRadius: 70,
|
||||
color: color,
|
||||
offset: const Offset(0, 18),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user