fixed pages
This commit is contained in:
@@ -43,3 +43,6 @@ app.*.map.json
|
|||||||
/android/app/debug
|
/android/app/debug
|
||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
|
|
||||||
|
key.properties
|
||||||
|
*.jks
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import java.util.Properties
|
||||||
|
import java.io.FileInputStream
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("kotlin-android")
|
id("kotlin-android")
|
||||||
@@ -5,6 +8,12 @@ plugins {
|
|||||||
id("dev.flutter.flutter-gradle-plugin")
|
id("dev.flutter.flutter-gradle-plugin")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val keystoreProperties = Properties()
|
||||||
|
val keystorePropertiesFile = rootProject.file("key.properties")
|
||||||
|
if (keystorePropertiesFile.exists()) {
|
||||||
|
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "it.yogasoul.yogibook"
|
namespace = "it.yogasoul.yogibook"
|
||||||
compileSdk = flutter.compileSdkVersion
|
compileSdk = flutter.compileSdkVersion
|
||||||
@@ -20,21 +29,25 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
|
||||||
applicationId = "it.yogasoul.yogibook"
|
applicationId = "it.yogasoul.yogibook"
|
||||||
// You can update the following values to match your application needs.
|
|
||||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
|
||||||
minSdk = flutter.minSdkVersion
|
minSdk = flutter.minSdkVersion
|
||||||
targetSdk = flutter.targetSdkVersion
|
targetSdk = flutter.targetSdkVersion
|
||||||
versionCode = flutter.versionCode
|
versionCode = flutter.versionCode
|
||||||
versionName = flutter.versionName
|
versionName = flutter.versionName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signingConfigs {
|
||||||
|
create("release") {
|
||||||
|
keyAlias = keystoreProperties["keyAlias"] as String
|
||||||
|
keyPassword = keystoreProperties["keyPassword"] as String
|
||||||
|
storeFile = keystoreProperties["storeFile"]?.let { file(it) }
|
||||||
|
storePassword = keystoreProperties["storePassword"] as String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
// TODO: Add your own signing config for the release build.
|
signingConfig = signingConfigs.getByName("release")
|
||||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
|
||||||
signingConfig = signingConfigs.getByName("debug")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import 'meditation_page.dart';
|
|||||||
import '../widgets/app_drawer.dart';
|
import '../widgets/app_drawer.dart';
|
||||||
import '../widgets/app_bottom_nav.dart';
|
import '../widgets/app_bottom_nav.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import '../widgets/yogibook_background.dart';
|
|
||||||
import 'reschedule_page.dart';
|
import 'reschedule_page.dart';
|
||||||
import 'orders_page.dart';
|
import 'orders_page.dart';
|
||||||
|
|
||||||
@@ -116,23 +115,10 @@ class _LessonsPageState extends State<LessonsPage> {
|
|||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
title: Column(
|
title: const Text(
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'Le mie lezioni',
|
'Le mie lezioni',
|
||||||
style: TextStyle(fontWeight: FontWeight.w800, fontSize: 18),
|
style: TextStyle(fontWeight: FontWeight.w800, fontSize: 18),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
|
||||||
Text(
|
|
||||||
widget.school.name,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Colors.black54,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
actions: [
|
actions: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right: 12),
|
padding: const EdgeInsets.only(right: 12),
|
||||||
@@ -158,7 +144,8 @@ class _LessonsPageState extends State<LessonsPage> {
|
|||||||
userFirstName: widget.userFirstName,
|
userFirstName: widget.userFirstName,
|
||||||
),
|
),
|
||||||
|
|
||||||
body: YogibookBackground(
|
body: Container(
|
||||||
|
color: const Color(0xFFE2FDFF),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
top: false,
|
top: false,
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ class _MeditationPageState extends State<MeditationPage>
|
|||||||
elevation: 0,
|
elevation: 0,
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
title: const Text(
|
title: const Text(
|
||||||
'Meditazione',
|
'Respira',
|
||||||
style: TextStyle(fontWeight: FontWeight.w800, fontSize: 18),
|
style: TextStyle(fontWeight: FontWeight.w800, fontSize: 18),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
|
|||||||
@@ -81,6 +81,11 @@ class _ReschedulePageState extends State<ReschedulePage> {
|
|||||||
return s[0].toUpperCase() + s.substring(1);
|
return s[0].toUpperCase() + s.substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _dateLabel(String ymd) {
|
||||||
|
final dt = DateFormat('yyyy-MM-dd').parse(ymd);
|
||||||
|
return DateFormat('dd/MM/yyyy').format(dt);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
const green = Color(0xFF10B981);
|
const green = Color(0xFF10B981);
|
||||||
@@ -176,6 +181,7 @@ class _ReschedulePageState extends State<ReschedulePage> {
|
|||||||
return _SlotCard(
|
return _SlotCard(
|
||||||
slot: s,
|
slot: s,
|
||||||
weekday: _weekdayLabel(s.date),
|
weekday: _weekdayLabel(s.date),
|
||||||
|
dateLabel: _dateLabel(s.date),
|
||||||
onChoose: () => _confirm(s),
|
onChoose: () => _confirm(s),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -239,11 +245,13 @@ class _ReschedulePageState extends State<ReschedulePage> {
|
|||||||
class _SlotCard extends StatelessWidget {
|
class _SlotCard extends StatelessWidget {
|
||||||
final AvailableSlot slot;
|
final AvailableSlot slot;
|
||||||
final String weekday;
|
final String weekday;
|
||||||
|
final String dateLabel;
|
||||||
final VoidCallback onChoose;
|
final VoidCallback onChoose;
|
||||||
|
|
||||||
const _SlotCard({
|
const _SlotCard({
|
||||||
required this.slot,
|
required this.slot,
|
||||||
required this.weekday,
|
required this.weekday,
|
||||||
|
required this.dateLabel,
|
||||||
required this.onChoose,
|
required this.onChoose,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -316,7 +324,7 @@ class _SlotCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
'$weekday ${slot.date} • ${slot.time}',
|
'$weekday $dateLabel • ${slot.time}',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Colors.black54,
|
color: Colors.black54,
|
||||||
@@ -325,10 +333,18 @@ class _SlotCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
Text(
|
Text(
|
||||||
'Posti liberi: ${slot.freePlaces}/${slot.maxCapacity}',
|
isBooked
|
||||||
|
? 'Già prenotato'
|
||||||
|
: isFull
|
||||||
|
? 'Non disponibile'
|
||||||
|
: 'Disponibile',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: isFull ? orange : Colors.black45,
|
color: isFull
|
||||||
|
? const Color(0xFF9AA0A6)
|
||||||
|
: isBooked
|
||||||
|
? orange
|
||||||
|
: green,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class _SplashPageState extends State<SplashPage> {
|
|||||||
static const Color kRed = Color(0xFFB01E28);
|
static const Color kRed = Color(0xFFB01E28);
|
||||||
|
|
||||||
Timer? _timer;
|
Timer? _timer;
|
||||||
int _seconds = 5; // countdown iniziale
|
int _seconds = 8; // countdown iniziale
|
||||||
|
|
||||||
// Evita doppie navigazioni: countdown, tap e sblocco possono
|
// Evita doppie navigazioni: countdown, tap e sblocco possono
|
||||||
// altrimenti innescare più push.
|
// altrimenti innescare più push.
|
||||||
@@ -176,7 +176,11 @@ class _SplashPageState extends State<SplashPage> {
|
|||||||
Positioned(
|
Positioned(
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
bottom: 30,
|
bottom: 0,
|
||||||
|
child: SafeArea(
|
||||||
|
top: false,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 20),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
@@ -216,6 +220,8 @@ class _SplashPageState extends State<SplashPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -109,6 +109,10 @@ class _TeacherAddParticipantPageState extends State<TeacherAddParticipantPage> {
|
|||||||
_mode = 'omaggio';
|
_mode = 'omaggio';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// DEBUG temporaneo — rimuovere dopo diagnosi
|
||||||
|
_snack('DEBUG ordini: ${o.length} totali, ${usable.length} con residui');
|
||||||
|
// DEBUG temporaneo
|
||||||
|
_snack('DEBUG: ${o.length} ordini totali, ${usable.length} con residui');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_snack('$e');
|
_snack('$e');
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -27,8 +27,9 @@ class _TeacherClassDetailPageState extends State<TeacherClassDetailPage> {
|
|||||||
|
|
||||||
// copia locale modificabile dei partecipanti (per aggiornare la UI)
|
// copia locale modificabile dei partecipanti (per aggiornare la UI)
|
||||||
late List<TeacherParticipant> _participants;
|
late List<TeacherParticipant> _participants;
|
||||||
int _busyBookingId =
|
int _busyBookingId = 0; // booking in aggiornamento
|
||||||
0; // booking in aggiornamento (per disabilitare il tasto)
|
bool _changed =
|
||||||
|
false; // true se ci sono state modifiche (per refresh lista)/ booking in aggiornamento (per disabilitare il tasto)
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -100,6 +101,7 @@ class _TeacherClassDetailPageState extends State<TeacherClassDetailPage> {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
_changed = true;
|
||||||
_snack(markLost ? 'Segnata come persa.' : 'Ripristinata.');
|
_snack(markLost ? 'Segnata come persa.' : 'Ripristinata.');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_snack('$e');
|
_snack('$e');
|
||||||
@@ -143,6 +145,7 @@ class _TeacherClassDetailPageState extends State<TeacherClassDetailPage> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_participants.removeWhere((x) => x.bookingId == p.bookingId);
|
_participants.removeWhere((x) => x.bookingId == p.bookingId);
|
||||||
});
|
});
|
||||||
|
_changed = true;
|
||||||
_snack('Prenotazione eliminata.');
|
_snack('Prenotazione eliminata.');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_snack('$e');
|
_snack('$e');
|
||||||
@@ -151,6 +154,129 @@ class _TeacherClassDetailPageState extends State<TeacherClassDetailPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _confirmPending(TeacherParticipant p) async {
|
||||||
|
setState(() => _busyBookingId = p.bookingId);
|
||||||
|
try {
|
||||||
|
await TeacherApi.confirmPending(
|
||||||
|
token: widget.token,
|
||||||
|
bookingId: p.bookingId,
|
||||||
|
);
|
||||||
|
// da pending a confermato: aggiorna lo stato locale
|
||||||
|
final idx = _participants.indexWhere((x) => x.bookingId == p.bookingId);
|
||||||
|
if (idx != -1) {
|
||||||
|
setState(() {
|
||||||
|
_participants[idx] = TeacherParticipant(
|
||||||
|
bookingId: p.bookingId,
|
||||||
|
fullName: p.fullName,
|
||||||
|
isLost: false,
|
||||||
|
isPending: false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_changed = true;
|
||||||
|
_snack('${p.fullName} confermato.');
|
||||||
|
} catch (e) {
|
||||||
|
_snack('$e');
|
||||||
|
} finally {
|
||||||
|
if (mounted) setState(() => _busyBookingId = 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _pendingTile(TeacherParticipant p) {
|
||||||
|
final busy = _busyBookingId == p.bookingId;
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFFFFBF0),
|
||||||
|
borderRadius: BorderRadius.circular(14),
|
||||||
|
border: Border.all(color: const Color(0xFFF5E6B8)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 42,
|
||||||
|
height: 42,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Color(0xFFE0A800),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
_initials(p.fullName),
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
fontSize: 15,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
p.fullName,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 15,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8,
|
||||||
|
vertical: 2,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFFFF4D6),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'In attesa',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
color: Color(0xFF8A6D00),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (busy)
|
||||||
|
const SizedBox(
|
||||||
|
width: 36,
|
||||||
|
height: 36,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else ...[
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => _confirmPending(p),
|
||||||
|
tooltip: 'Conferma',
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.check_circle_outline_rounded,
|
||||||
|
color: Color(0xFF1A7F52),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => _deleteBooking(p),
|
||||||
|
tooltip: 'Rimuovi',
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.delete_outline_rounded,
|
||||||
|
color: Colors.black45,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _reprogram(TeacherParticipant p) async {
|
Future<void> _reprogram(TeacherParticipant p) async {
|
||||||
final done = await Navigator.push<bool>(
|
final done = await Navigator.push<bool>(
|
||||||
context,
|
context,
|
||||||
@@ -173,15 +299,27 @@ class _TeacherClassDetailPageState extends State<TeacherClassDetailPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final active = _participants.where((p) => !p.isLost).toList();
|
final pending = _participants.where((p) => p.isPending).toList();
|
||||||
final lost = _participants.where((p) => p.isLost).toList();
|
final active = _participants
|
||||||
|
.where((p) => !p.isPending && !p.isLost)
|
||||||
|
.toList();
|
||||||
|
final lost = _participants.where((p) => !p.isPending && p.isLost).toList();
|
||||||
final cls = widget.cls;
|
final cls = widget.cls;
|
||||||
|
|
||||||
return Scaffold(
|
return PopScope(
|
||||||
|
canPop: true,
|
||||||
|
onPopInvokedWithResult: (didPop, result) {
|
||||||
|
// quando si esce, se ci sono state modifiche la lista si aggiorna
|
||||||
|
},
|
||||||
|
child: Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
onPressed: () => Navigator.pop(context, _changed),
|
||||||
|
),
|
||||||
title: const Text(
|
title: const Text(
|
||||||
'Dettaglio classe',
|
'Dettaglio classe',
|
||||||
style: TextStyle(fontWeight: FontWeight.w800, fontSize: 18),
|
style: TextStyle(fontWeight: FontWeight.w800, fontSize: 18),
|
||||||
@@ -195,6 +333,12 @@ class _TeacherClassDetailPageState extends State<TeacherClassDetailPage> {
|
|||||||
children: [
|
children: [
|
||||||
_headerCard(),
|
_headerCard(),
|
||||||
const SizedBox(height: 18),
|
const SizedBox(height: 18),
|
||||||
|
if (pending.isNotEmpty) ...[
|
||||||
|
_sectionLabel('In attesa di conferma (${pending.length})'),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
...pending.map((p) => _pendingTile(p)),
|
||||||
|
const SizedBox(height: 18),
|
||||||
|
],
|
||||||
_sectionLabel('Iscritti (${active.length})'),
|
_sectionLabel('Iscritti (${active.length})'),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
if (active.isEmpty)
|
if (active.isEmpty)
|
||||||
@@ -218,7 +362,10 @@ class _TeacherClassDetailPageState extends State<TeacherClassDetailPage> {
|
|||||||
borderRadius: BorderRadius.circular(14),
|
borderRadius: BorderRadius.circular(14),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
icon: const Icon(Icons.person_add_alt_1, color: Colors.white),
|
icon: const Icon(
|
||||||
|
Icons.person_add_alt_1,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
label: const Text(
|
label: const Text(
|
||||||
'Aggiungi partecipante',
|
'Aggiungi partecipante',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -233,6 +380,7 @@ class _TeacherClassDetailPageState extends State<TeacherClassDetailPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,10 +393,8 @@ class _TeacherClassDetailPageState extends State<TeacherClassDetailPage> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (added == true && mounted) {
|
if (added == true && mounted) {
|
||||||
_snack(
|
_changed = true;
|
||||||
'Partecipante aggiunto. Aggiorna il pannello per vedere la lista.',
|
_snack('Partecipante aggiunto.');
|
||||||
);
|
|
||||||
// torna al pannello classi, che ricaricando mostrerà i dati aggiornati
|
|
||||||
Navigator.pop(context, true);
|
Navigator.pop(context, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -392,6 +392,27 @@ class _ClassCard extends StatelessWidget {
|
|||||||
color: fillColor,
|
color: fillColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (cls.participants.any((p) => p.isPending)) ...[
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 6,
|
||||||
|
vertical: 2,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFFFF4D6),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'${cls.participants.where((p) => p.isPending).length} in attesa',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
color: Color(0xFF8A6D00),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -9,11 +9,13 @@ class TeacherParticipant {
|
|||||||
final int bookingId;
|
final int bookingId;
|
||||||
final String fullName;
|
final String fullName;
|
||||||
final bool isLost;
|
final bool isLost;
|
||||||
|
final bool isPending;
|
||||||
|
|
||||||
const TeacherParticipant({
|
const TeacherParticipant({
|
||||||
required this.bookingId,
|
required this.bookingId,
|
||||||
required this.fullName,
|
required this.fullName,
|
||||||
required this.isLost,
|
required this.isLost,
|
||||||
|
this.isPending = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory TeacherParticipant.fromMap(Map<String, dynamic> m) =>
|
factory TeacherParticipant.fromMap(Map<String, dynamic> m) =>
|
||||||
@@ -21,6 +23,7 @@ class TeacherParticipant {
|
|||||||
bookingId: (m['booking_id'] as num?)?.toInt() ?? 0,
|
bookingId: (m['booking_id'] as num?)?.toInt() ?? 0,
|
||||||
fullName: (m['full_name'] ?? '').toString().trim(),
|
fullName: (m['full_name'] ?? '').toString().trim(),
|
||||||
isLost: m['is_lost'] == true,
|
isLost: m['is_lost'] == true,
|
||||||
|
isPending: m['is_pending'] == true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,4 +410,35 @@ class TeacherApi {
|
|||||||
throw Exception(data['error'] ?? data['message'] ?? 'Errore inserimento');
|
throw Exception(data['error'] ?? data['message'] ?? 'Errore inserimento');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Conferma una prenotazione in attesa (pending -> booked).
|
||||||
|
static Future<void> confirmPending({
|
||||||
|
required String token,
|
||||||
|
required int bookingId,
|
||||||
|
}) async {
|
||||||
|
final uri = Uri.parse(
|
||||||
|
'${ApiConfig.laravelApiBase}/api_teacher_confirm_pending.php',
|
||||||
|
);
|
||||||
|
|
||||||
|
final res = await http.post(
|
||||||
|
uri,
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
'Authorization': 'Bearer $token',
|
||||||
|
},
|
||||||
|
body: 'booking_id=$bookingId',
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> data;
|
||||||
|
try {
|
||||||
|
data = jsonDecode(res.body) as Map<String, dynamic>;
|
||||||
|
} catch (_) {
|
||||||
|
throw Exception('RAW(${res.statusCode}): ${res.body}');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.statusCode != 200 || data['success'] != true) {
|
||||||
|
throw Exception(data['error'] ?? data['message'] ?? 'Errore conferma');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class AppBottomNav extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: Icon(Icons.self_improvement_rounded),
|
icon: Icon(Icons.self_improvement_rounded),
|
||||||
label: 'Meditazione',
|
label: 'Respira',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import '../config/api_config.dart';
|
|||||||
|
|
||||||
import '../screens/login_page.dart';
|
import '../screens/login_page.dart';
|
||||||
import '../screens/medical_certificates_page.dart';
|
import '../screens/medical_certificates_page.dart';
|
||||||
import '../screens/lessons_page.dart';
|
|
||||||
import '../screens/orders_detail_page.dart';
|
import '../screens/orders_detail_page.dart';
|
||||||
import '../screens/account_page.dart';
|
|
||||||
import '../screens/settings_page.dart';
|
import '../screens/settings_page.dart';
|
||||||
import '../screens/teacher_panel_page.dart';
|
import '../screens/teacher_panel_page.dart';
|
||||||
import '../services/role_service.dart';
|
import '../services/role_service.dart';
|
||||||
@@ -159,24 +159,6 @@ class AppDrawer extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.event_note_rounded),
|
|
||||||
title: const Text('Lezioni'),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
Navigator.pushReplacement(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (_) => LessonsPage(
|
|
||||||
token: token,
|
|
||||||
school: school,
|
|
||||||
userFirstName: userFirstName,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.shopping_bag_rounded),
|
leading: const Icon(Icons.shopping_bag_rounded),
|
||||||
title: const Text('Ordini'),
|
title: const Text('Ordini'),
|
||||||
@@ -195,24 +177,6 @@ class AppDrawer extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.person_rounded),
|
|
||||||
title: const Text('Account'),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (_) => AccountPage(
|
|
||||||
token: token,
|
|
||||||
school: school,
|
|
||||||
userFirstName: userFirstName,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.settings_rounded),
|
leading: const Icon(Icons.settings_rounded),
|
||||||
title: const Text('Impostazioni'),
|
title: const Text('Impostazioni'),
|
||||||
@@ -235,10 +199,13 @@ class AppDrawer extends StatelessWidget {
|
|||||||
|
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.privacy_tip_outlined),
|
leading: const Icon(Icons.privacy_tip_outlined),
|
||||||
title: const Text('Cookie e Privacy Policy'),
|
title: const Text('Privacy Policy'),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
_openUrl(context, 'https://yogasoul.it/cookie-policy/');
|
_openUrl(
|
||||||
|
context,
|
||||||
|
'https://yogasoul.it/privacy-policy-yogibook/',
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|||||||
@@ -9,27 +9,19 @@ class YogibookBackground extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
// --- SFONDO (gradient)
|
// --- SFONDO (tinta piatta)
|
||||||
Container(
|
Container(color: const Color(0xFFE2FDFF)),
|
||||||
decoration: const BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
colors: [Color(0xFFF4F6FA), Color(0xFFEFF7F3), Color(0xFFF4F6FA)],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// --- BOLLE decorative
|
// --- BOLLE decorative
|
||||||
const _BgBlob(color: Color(0x3310B981), size: 280, top: -70, left: -90),
|
const _BgBlob(color: Color(0x9962BBC1), size: 280, top: -70, left: -90),
|
||||||
const _BgBlob(
|
const _BgBlob(
|
||||||
color: Color(0x22F59E0B),
|
color: Color(0x99F4ECE1),
|
||||||
size: 230,
|
size: 230,
|
||||||
bottom: 80,
|
bottom: 80,
|
||||||
right: -70,
|
right: -70,
|
||||||
),
|
),
|
||||||
const _BgBlob(
|
const _BgBlob(
|
||||||
color: Color(0x227C3AED),
|
color: Color(0x99E1D0B8),
|
||||||
size: 190,
|
size: 190,
|
||||||
bottom: -55,
|
bottom: -55,
|
||||||
left: 35,
|
left: 35,
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
name: yogibook_app
|
name: yogibook_app
|
||||||
description: "A new Flutter project."
|
description: "Prenotazione YogaSoul"
|
||||||
# The following line prevents the package from being accidentally published to
|
# The following line prevents the package from being accidentally published to
|
||||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
||||||
publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||||
|
|||||||
Reference in New Issue
Block a user