Refacto et clean
This commit is contained in:
@ -6,16 +6,16 @@ class ResetPasswordPage extends StatefulWidget {
|
|||||||
final String actionCode;
|
final String actionCode;
|
||||||
|
|
||||||
const ResetPasswordPage({
|
const ResetPasswordPage({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.email,
|
required this.email,
|
||||||
required this.actionCode,
|
required this.actionCode,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_ResetPasswordPageState createState() => _ResetPasswordPageState();
|
ResetPasswordPageState createState() => ResetPasswordPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ResetPasswordPageState extends State<ResetPasswordPage> {
|
class ResetPasswordPageState extends State<ResetPasswordPage> {
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
final _passwordController = TextEditingController();
|
final _passwordController = TextEditingController();
|
||||||
final _confirmPasswordController = TextEditingController();
|
final _confirmPasswordController = TextEditingController();
|
||||||
|
115
em2rp/lib/utils/calendar_utils.dart
Normal file
115
em2rp/lib/utils/calendar_utils.dart
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import 'package:em2rp/models/event_model.dart';
|
||||||
|
|
||||||
|
class CalendarUtils {
|
||||||
|
static String getDayName(int weekday) {
|
||||||
|
switch (weekday) {
|
||||||
|
case DateTime.monday:
|
||||||
|
return 'Lundi';
|
||||||
|
case DateTime.tuesday:
|
||||||
|
return 'Mardi';
|
||||||
|
case DateTime.wednesday:
|
||||||
|
return 'Mercredi';
|
||||||
|
case DateTime.thursday:
|
||||||
|
return 'Jeudi';
|
||||||
|
case DateTime.friday:
|
||||||
|
return 'Vendredi';
|
||||||
|
case DateTime.saturday:
|
||||||
|
return 'Samedi';
|
||||||
|
case DateTime.sunday:
|
||||||
|
return 'Dimanche';
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getShortDayName(int weekday) {
|
||||||
|
switch (weekday) {
|
||||||
|
case DateTime.monday:
|
||||||
|
return 'Lun';
|
||||||
|
case DateTime.tuesday:
|
||||||
|
return 'Mar';
|
||||||
|
case DateTime.wednesday:
|
||||||
|
return 'Mer';
|
||||||
|
case DateTime.thursday:
|
||||||
|
return 'Jeu';
|
||||||
|
case DateTime.friday:
|
||||||
|
return 'Ven';
|
||||||
|
case DateTime.saturday:
|
||||||
|
return 'Sam';
|
||||||
|
case DateTime.sunday:
|
||||||
|
return 'Dim';
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getMonthName(int month) {
|
||||||
|
switch (month) {
|
||||||
|
case 1:
|
||||||
|
return 'Janvier';
|
||||||
|
case 2:
|
||||||
|
return 'Février';
|
||||||
|
case 3:
|
||||||
|
return 'Mars';
|
||||||
|
case 4:
|
||||||
|
return 'Avril';
|
||||||
|
case 5:
|
||||||
|
return 'Mai';
|
||||||
|
case 6:
|
||||||
|
return 'Juin';
|
||||||
|
case 7:
|
||||||
|
return 'Juillet';
|
||||||
|
case 8:
|
||||||
|
return 'Août';
|
||||||
|
case 9:
|
||||||
|
return 'Septembre';
|
||||||
|
case 10:
|
||||||
|
return 'Octobre';
|
||||||
|
case 11:
|
||||||
|
return 'Novembre';
|
||||||
|
case 12:
|
||||||
|
return 'Décembre';
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getMonthYearString(DateTime weekStart, DateTime weekEnd) {
|
||||||
|
if (weekStart.month == weekEnd.month) {
|
||||||
|
return '${getMonthName(weekStart.month)} ${weekStart.year}';
|
||||||
|
} else {
|
||||||
|
return '${getMonthName(weekStart.month)} - ${getMonthName(weekEnd.month)} ${weekEnd.year}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isMultiDayEvent(EventModel event) {
|
||||||
|
return event.startDateTime.day != event.endDateTime.day ||
|
||||||
|
event.startDateTime.month != event.endDateTime.month ||
|
||||||
|
event.startDateTime.year != event.endDateTime.year;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int calculateTotalDays(EventModel event) {
|
||||||
|
final startDate = DateTime(event.startDateTime.year,
|
||||||
|
event.startDateTime.month, event.startDateTime.day);
|
||||||
|
final endDate = DateTime(
|
||||||
|
event.endDateTime.year, event.endDateTime.month, event.endDateTime.day);
|
||||||
|
return endDate.difference(startDate).inDays + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int calculateDayNumber(DateTime startDate, DateTime currentDay) {
|
||||||
|
final start = DateTime(startDate.year, startDate.month, startDate.day);
|
||||||
|
final current = DateTime(currentDay.year, currentDay.month, currentDay.day);
|
||||||
|
return current.difference(start).inDays + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<EventModel> getEventsForDay(
|
||||||
|
DateTime day, List<EventModel> events) {
|
||||||
|
final dayStart = DateTime(day.year, day.month, day.day, 0, 0);
|
||||||
|
final dayEnd = DateTime(day.year, day.month, day.day, 23, 59, 59);
|
||||||
|
|
||||||
|
return events.where((event) {
|
||||||
|
return !(event.endDateTime.isBefore(dayStart) ||
|
||||||
|
event.startDateTime.isAfter(dayEnd));
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
}
|
@ -27,8 +27,6 @@ class LoginViewModel extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final userCredential = await localAuthProvider.signInWithEmailAndPassword(
|
|
||||||
emailController.text.trim(), passwordController.text);
|
|
||||||
print('User signed in');
|
print('User signed in');
|
||||||
|
|
||||||
// Attendre que les données utilisateur soient chargées
|
// Attendre que les données utilisateur soient chargées
|
||||||
|
@ -4,16 +4,15 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:em2rp/widgets/custom_app_bar.dart';
|
import 'package:em2rp/widgets/custom_app_bar.dart';
|
||||||
import 'package:em2rp/views/widgets/nav/main_drawer.dart';
|
import 'package:em2rp/views/widgets/nav/main_drawer.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:em2rp/utils/colors.dart';
|
|
||||||
import 'package:table_calendar/table_calendar.dart';
|
import 'package:table_calendar/table_calendar.dart';
|
||||||
import 'package:em2rp/models/event_model.dart';
|
import 'package:em2rp/models/event_model.dart';
|
||||||
import 'package:em2rp/widgets/event_details.dart';
|
import 'package:em2rp/widgets/event_details.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
|
||||||
import 'package:intl/date_symbol_data_local.dart';
|
import 'package:intl/date_symbol_data_local.dart';
|
||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
import 'package:em2rp/views/widgets/calendar_widgets/month_view.dart';
|
||||||
|
import 'package:em2rp/views/widgets/calendar_widgets/week_view.dart';
|
||||||
|
|
||||||
class CalendarPage extends StatefulWidget {
|
class CalendarPage extends StatefulWidget {
|
||||||
const CalendarPage({Key? key}) : super(key: key);
|
const CalendarPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CalendarPage> createState() => _CalendarPageState();
|
State<CalendarPage> createState() => _CalendarPageState();
|
||||||
@ -51,7 +50,6 @@ class _CalendarPageState extends State<CalendarPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final localAuthProvider = Provider.of<LocalUserProvider>(context);
|
|
||||||
final eventProvider = Provider.of<EventProvider>(context);
|
final eventProvider = Provider.of<EventProvider>(context);
|
||||||
final isMobile = MediaQuery.of(context).size.width < 600;
|
final isMobile = MediaQuery.of(context).size.width < 600;
|
||||||
|
|
||||||
@ -111,41 +109,30 @@ class _CalendarPageState extends State<CalendarPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCalendar() {
|
Widget _buildCalendar() {
|
||||||
return LayoutBuilder(
|
final eventProvider = Provider.of<EventProvider>(context);
|
||||||
builder: (context, constraints) {
|
|
||||||
if (_calendarFormat == CalendarFormat.week) {
|
if (_calendarFormat == CalendarFormat.week) {
|
||||||
return _buildWeekView(constraints);
|
return WeekView(
|
||||||
} else {
|
focusedDay: _focusedDay,
|
||||||
return _buildMonthView(constraints);
|
events: eventProvider.events,
|
||||||
}
|
onWeekChange: _changeWeek,
|
||||||
|
onEventSelected: (event) {
|
||||||
|
setState(() {
|
||||||
|
_selectedEvent = event;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSwitchToMonth: () {
|
||||||
|
setState(() {
|
||||||
|
_calendarFormat = CalendarFormat.month;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
} else {
|
||||||
|
return MonthView(
|
||||||
Widget _buildMonthView(BoxConstraints constraints) {
|
|
||||||
// Calculer la hauteur des lignes en fonction de la hauteur disponible
|
|
||||||
// Ajustement pour les mois à 6 semaines
|
|
||||||
final rowHeight = (constraints.maxHeight - 100) /
|
|
||||||
6; // Augmenté de 80 à 100 pour donner plus d'espace
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
height: constraints.maxHeight,
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.all(8), // Réduit de 16 à 8 pour gagner de l'espace
|
|
||||||
child: TableCalendar(
|
|
||||||
firstDay: DateTime.utc(2020, 1, 1),
|
|
||||||
lastDay: DateTime.utc(2030, 12, 31),
|
|
||||||
focusedDay: _focusedDay,
|
focusedDay: _focusedDay,
|
||||||
|
selectedDay: _selectedDay,
|
||||||
calendarFormat: _calendarFormat,
|
calendarFormat: _calendarFormat,
|
||||||
startingDayOfWeek: StartingDayOfWeek.monday,
|
events: eventProvider.events,
|
||||||
locale: 'fr_FR',
|
|
||||||
availableCalendarFormats: const {
|
|
||||||
CalendarFormat.month: 'Mois',
|
|
||||||
CalendarFormat.week: 'Semaine',
|
|
||||||
},
|
|
||||||
selectedDayPredicate: (day) {
|
|
||||||
return isSameDay(_selectedDay, day);
|
|
||||||
},
|
|
||||||
onDaySelected: (selectedDay, focusedDay) {
|
onDaySelected: (selectedDay, focusedDay) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedDay = selectedDay;
|
_selectedDay = selectedDay;
|
||||||
@ -158,714 +145,16 @@ class _CalendarPageState extends State<CalendarPage> {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
onPageChanged: (focusedDay) {
|
onPageChanged: (focusedDay) {
|
||||||
|
setState(() {
|
||||||
_focusedDay = focusedDay;
|
_focusedDay = focusedDay;
|
||||||
},
|
|
||||||
calendarStyle: CalendarStyle(
|
|
||||||
defaultDecoration: BoxDecoration(
|
|
||||||
border: Border.all(color: Colors.grey.shade300),
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
),
|
|
||||||
selectedDecoration: BoxDecoration(
|
|
||||||
color: AppColors.rouge,
|
|
||||||
border: Border.all(color: AppColors.rouge),
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
),
|
|
||||||
todayDecoration: BoxDecoration(
|
|
||||||
color: AppColors.rouge.withOpacity(0.1),
|
|
||||||
border: Border.all(color: AppColors.rouge),
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
),
|
|
||||||
outsideDecoration: BoxDecoration(
|
|
||||||
border: Border.all(color: Colors.grey.shade300),
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
),
|
|
||||||
outsideDaysVisible: false,
|
|
||||||
cellMargin: EdgeInsets.zero,
|
|
||||||
cellPadding: EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
rowHeight: rowHeight,
|
|
||||||
headerStyle: HeaderStyle(
|
|
||||||
formatButtonVisible: true,
|
|
||||||
titleCentered: true,
|
|
||||||
formatButtonShowsNext: false,
|
|
||||||
formatButtonDecoration: BoxDecoration(
|
|
||||||
color: AppColors.rouge,
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
),
|
|
||||||
formatButtonTextStyle: const TextStyle(color: Colors.white),
|
|
||||||
leftChevronIcon:
|
|
||||||
const Icon(Icons.chevron_left, color: AppColors.rouge),
|
|
||||||
rightChevronIcon:
|
|
||||||
const Icon(Icons.chevron_right, color: AppColors.rouge),
|
|
||||||
headerPadding: const EdgeInsets.symmetric(vertical: 8),
|
|
||||||
titleTextStyle: const TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
calendarBuilders: CalendarBuilders(
|
|
||||||
dowBuilder: (context, day) {
|
|
||||||
return Center(
|
|
||||||
child: Text(
|
|
||||||
_getDayName(day.weekday),
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
defaultBuilder: (context, day, focusedDay) {
|
|
||||||
final events = _getEventsForDay(day);
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
margin: const EdgeInsets.all(4),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border.all(color: Colors.grey.shade300),
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
),
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
// Numéro du jour en haut à gauche
|
|
||||||
Positioned(
|
|
||||||
top: 4,
|
|
||||||
left: 4,
|
|
||||||
child: Text(
|
|
||||||
day.day.toString(),
|
|
||||||
style: TextStyle(
|
|
||||||
color:
|
|
||||||
isSameDay(day, _selectedDay) ? Colors.white : null,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// Badge du nombre d'événements en haut à droite
|
|
||||||
if (events.isNotEmpty)
|
|
||||||
Positioned(
|
|
||||||
top: 4,
|
|
||||||
right: 4,
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 6, vertical: 2),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: isSameDay(day, _selectedDay)
|
|
||||||
? Colors.white
|
|
||||||
: AppColors.rouge,
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
events.length.toString(),
|
|
||||||
style: TextStyle(
|
|
||||||
color: isSameDay(day, _selectedDay)
|
|
||||||
? AppColors.rouge
|
|
||||||
: Colors.white,
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// Liste des événements en dessous
|
|
||||||
if (events.isNotEmpty)
|
|
||||||
Positioned(
|
|
||||||
bottom: 2,
|
|
||||||
left: 2,
|
|
||||||
right: 2,
|
|
||||||
top: 28,
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: events
|
|
||||||
.map((event) => GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
setState(() {
|
|
||||||
_selectedEvent = event;
|
|
||||||
_selectedDay = day;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: Container(
|
onEventSelected: (event) {
|
||||||
margin: const EdgeInsets.only(bottom: 2),
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 4, vertical: 2),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColors.rouge.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment:
|
|
||||||
CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
event.name,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: AppColors.rouge,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
if (_isMultiDayEvent(event))
|
|
||||||
Text(
|
|
||||||
'Jour ${_calculateDayNumber(event.startDateTime, day)}/${_calculateTotalDays(event)}',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 10,
|
|
||||||
color: AppColors.rouge,
|
|
||||||
),
|
|
||||||
maxLines: 1,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
selectedBuilder: (context, day, focusedDay) {
|
|
||||||
final events = _getEventsForDay(day);
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
margin: const EdgeInsets.all(4),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColors.rouge,
|
|
||||||
border: Border.all(color: AppColors.rouge),
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
),
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
// Numéro du jour en haut à gauche
|
|
||||||
Positioned(
|
|
||||||
top: 4,
|
|
||||||
left: 4,
|
|
||||||
child: Text(
|
|
||||||
day.day.toString(),
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// Badge du nombre d'événements en haut à droite
|
|
||||||
if (events.isNotEmpty)
|
|
||||||
Positioned(
|
|
||||||
top: 4,
|
|
||||||
right: 4,
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 6, vertical: 2),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
events.length.toString(),
|
|
||||||
style: const TextStyle(
|
|
||||||
color: AppColors.rouge,
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// Liste des événements en dessous
|
|
||||||
if (events.isNotEmpty)
|
|
||||||
Positioned(
|
|
||||||
bottom: 2,
|
|
||||||
left: 2,
|
|
||||||
right: 2,
|
|
||||||
top: 28,
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: events
|
|
||||||
.map((event) => GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedEvent = event;
|
_selectedEvent = event;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: Container(
|
|
||||||
margin: const EdgeInsets.only(bottom: 2),
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 4, vertical: 2),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white.withOpacity(0.2),
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment:
|
|
||||||
CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
event.name,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
if (_isMultiDayEvent(event))
|
|
||||||
Text(
|
|
||||||
'Jour ${_calculateDayNumber(event.startDateTime, day)}/${_calculateTotalDays(event)}',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 10,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
maxLines: 1,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildWeekView(BoxConstraints constraints) {
|
|
||||||
final weekStart =
|
|
||||||
_focusedDay.subtract(Duration(days: _focusedDay.weekday - 1));
|
|
||||||
final weekEnd = weekStart.add(const Duration(days: 6));
|
|
||||||
|
|
||||||
// Ajustement de la hauteur pour éviter l'overflow
|
|
||||||
double availableHeight =
|
|
||||||
constraints.maxHeight - 80; // Réserver de l'espace pour les en-têtes
|
|
||||||
final hourHeight = availableHeight / 24;
|
|
||||||
final dayWidth = (constraints.maxWidth - 50) / 7;
|
|
||||||
|
|
||||||
// Préparer les événements par jour (en tenant compte des multi-jours)
|
|
||||||
List<List<_PositionedEvent>> eventsByDay = List.generate(7, (i) => []);
|
|
||||||
for (final event in Provider.of<EventProvider>(context).events) {
|
|
||||||
// Pour chaque jour de la semaine
|
|
||||||
for (int i = 0; i < 7; i++) {
|
|
||||||
final day = weekStart.add(Duration(days: i));
|
|
||||||
final dayStart = DateTime(day.year, day.month, day.day, 0, 0);
|
|
||||||
final dayEnd = DateTime(day.year, day.month, day.day, 23, 59, 59);
|
|
||||||
// Si l'événement recouvre ce jour
|
|
||||||
if (!(event.endDateTime.isBefore(dayStart) ||
|
|
||||||
event.startDateTime.isAfter(dayEnd))) {
|
|
||||||
// Tronquer les heures de début/fin si besoin
|
|
||||||
final start = event.startDateTime.isBefore(dayStart)
|
|
||||||
? dayStart
|
|
||||||
: event.startDateTime;
|
|
||||||
final end =
|
|
||||||
event.endDateTime.isAfter(dayEnd) ? dayEnd : event.endDateTime;
|
|
||||||
eventsByDay[i].add(_PositionedEvent(event, start, end));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pour chaque jour, calculer les "colonnes" d'événements qui se chevauchent
|
|
||||||
List<List<_PositionedEventWithColumn>> eventsWithColumnsByDay = [];
|
|
||||||
for (final dayEvents in eventsByDay) {
|
|
||||||
final columns = _assignColumns(dayEvents);
|
|
||||||
eventsWithColumnsByDay.add(columns);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
// Barre d'en-tête semaine
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.chevron_left),
|
|
||||||
onPressed: () => _changeWeek(-1),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
_getMonthYearString(weekStart, weekEnd),
|
|
||||||
style:
|
|
||||||
const TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
_calendarFormat = CalendarFormat.month;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
backgroundColor: AppColors.rouge,
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
),
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 12, vertical: 8),
|
|
||||||
),
|
|
||||||
child: const Text('Semaine'),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.chevron_right),
|
|
||||||
onPressed: () => _changeWeek(1),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// En-tête avec les jours
|
|
||||||
SizedBox(
|
|
||||||
height: 40,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 50,
|
|
||||||
color: Colors.transparent,
|
|
||||||
),
|
|
||||||
...List.generate(7, (index) {
|
|
||||||
final day = weekStart.add(Duration(days: index));
|
|
||||||
return Container(
|
|
||||||
width: dayWidth,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border(
|
|
||||||
right: BorderSide(
|
|
||||||
color: index < 6
|
|
||||||
? Colors.grey.shade300
|
|
||||||
: Colors.transparent,
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(_getDayName(day.weekday),
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.bold)),
|
|
||||||
Text('${day.day}',
|
|
||||||
style: const TextStyle(fontSize: 13)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (_getEventsForDay(day).isNotEmpty)
|
|
||||||
Positioned(
|
|
||||||
top: 4,
|
|
||||||
right: 4,
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 6, vertical: 2),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColors.rouge,
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
_getEventsForDay(day).length.toString(),
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 10,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// Grille des heures + jours
|
|
||||||
Expanded(
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: SizedBox(
|
|
||||||
height: 24 * hourHeight,
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// Colonne des heures
|
|
||||||
Column(
|
|
||||||
children: List.generate(24, (index) {
|
|
||||||
return Container(
|
|
||||||
width: 50,
|
|
||||||
height: hourHeight,
|
|
||||||
alignment: Alignment.topRight,
|
|
||||||
padding: const EdgeInsets.only(right: 4),
|
|
||||||
child: Text(
|
|
||||||
'${index.toString().padLeft(2, '0')}:00',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.grey.shade600,
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
// Grille des jours
|
|
||||||
Expanded(
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
// Lignes horizontales
|
|
||||||
Column(
|
|
||||||
children: List.generate(24, (index) {
|
|
||||||
return Container(
|
|
||||||
height: hourHeight,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border(
|
|
||||||
bottom: BorderSide(
|
|
||||||
color: Colors.grey.shade300,
|
|
||||||
width: 0.5,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
// Bordures verticales entre jours
|
|
||||||
Positioned.fill(
|
|
||||||
child: Row(
|
|
||||||
children: List.generate(7, (i) {
|
|
||||||
return Container(
|
|
||||||
width: dayWidth,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border(
|
|
||||||
right: BorderSide(
|
|
||||||
color: i < 6
|
|
||||||
? Colors.grey.shade300
|
|
||||||
: Colors.transparent,
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// Événements (chevauchements et multi-jours)
|
|
||||||
...List.generate(7, (dayIdx) {
|
|
||||||
final dayEvents = eventsWithColumnsByDay[dayIdx];
|
|
||||||
return Stack(
|
|
||||||
children: dayEvents.map((e) {
|
|
||||||
final startHour =
|
|
||||||
e.start.hour + e.start.minute / 60;
|
|
||||||
final endHour = e.end.hour + e.end.minute / 60;
|
|
||||||
final duration = endHour - startHour;
|
|
||||||
final width = dayWidth / e.totalColumns;
|
|
||||||
return Positioned(
|
|
||||||
left: dayIdx * dayWidth + e.column * width,
|
|
||||||
top: startHour * hourHeight,
|
|
||||||
width: width,
|
|
||||||
height: duration * hourHeight,
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
setState(() {
|
|
||||||
_selectedEvent = e.event;
|
|
||||||
_selectedDay =
|
|
||||||
weekStart.add(Duration(days: dayIdx));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
margin: const EdgeInsets.all(2),
|
|
||||||
padding: const EdgeInsets.all(4),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColors.rouge.withOpacity(0.2),
|
|
||||||
border:
|
|
||||||
Border.all(color: AppColors.rouge),
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment:
|
|
||||||
CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
e.event.name,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: AppColors.rouge,
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
if (_isMultiDayEvent(e.event))
|
|
||||||
Text(
|
|
||||||
'Jour ${_calculateDayNumber(e.event.startDateTime, weekStart.add(Duration(days: dayIdx)))}/${_calculateTotalDays(e.event)}',
|
|
||||||
style: const TextStyle(
|
|
||||||
color: AppColors.rouge,
|
|
||||||
fontSize: 10,
|
|
||||||
),
|
|
||||||
maxLines: 1,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Méthode pour récupérer les événements pour un jour donné (inclut les multi-jours)
|
|
||||||
List<EventModel> _getEventsForDay(DateTime day) {
|
|
||||||
final dayStart = DateTime(day.year, day.month, day.day, 0, 0);
|
|
||||||
final dayEnd = DateTime(day.year, day.month, day.day, 23, 59, 59);
|
|
||||||
|
|
||||||
return Provider.of<EventProvider>(context).events.where((event) {
|
|
||||||
return !(event.endDateTime.isBefore(dayStart) ||
|
|
||||||
event.startDateTime.isAfter(dayEnd));
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Méthodes pour gérer les événements multi-jours
|
|
||||||
bool _isMultiDayEvent(EventModel event) {
|
|
||||||
return event.startDateTime.day != event.endDateTime.day ||
|
|
||||||
event.startDateTime.month != event.endDateTime.month ||
|
|
||||||
event.startDateTime.year != event.endDateTime.year;
|
|
||||||
}
|
|
||||||
|
|
||||||
int _calculateTotalDays(EventModel event) {
|
|
||||||
final startDate = DateTime(event.startDateTime.year,
|
|
||||||
event.startDateTime.month, event.startDateTime.day);
|
|
||||||
final endDate = DateTime(
|
|
||||||
event.endDateTime.year, event.endDateTime.month, event.endDateTime.day);
|
|
||||||
return endDate.difference(startDate).inDays + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int _calculateDayNumber(DateTime startDate, DateTime currentDay) {
|
|
||||||
final start = DateTime(startDate.year, startDate.month, startDate.day);
|
|
||||||
final current = DateTime(currentDay.year, currentDay.month, currentDay.day);
|
|
||||||
return current.difference(start).inDays + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
String _getDayName(int weekday) {
|
|
||||||
switch (weekday) {
|
|
||||||
case DateTime.monday:
|
|
||||||
return 'Lun';
|
|
||||||
case DateTime.tuesday:
|
|
||||||
return 'Mar';
|
|
||||||
case DateTime.wednesday:
|
|
||||||
return 'Mer';
|
|
||||||
case DateTime.thursday:
|
|
||||||
return 'Jeu';
|
|
||||||
case DateTime.friday:
|
|
||||||
return 'Ven';
|
|
||||||
case DateTime.saturday:
|
|
||||||
return 'Sam';
|
|
||||||
case DateTime.sunday:
|
|
||||||
return 'Dim';
|
|
||||||
default:
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String _getMonthName(int month) {
|
|
||||||
switch (month) {
|
|
||||||
case 1:
|
|
||||||
return 'Janvier';
|
|
||||||
case 2:
|
|
||||||
return 'Février';
|
|
||||||
case 3:
|
|
||||||
return 'Mars';
|
|
||||||
case 4:
|
|
||||||
return 'Avril';
|
|
||||||
case 5:
|
|
||||||
return 'Mai';
|
|
||||||
case 6:
|
|
||||||
return 'Juin';
|
|
||||||
case 7:
|
|
||||||
return 'Juillet';
|
|
||||||
case 8:
|
|
||||||
return 'Août';
|
|
||||||
case 9:
|
|
||||||
return 'Septembre';
|
|
||||||
case 10:
|
|
||||||
return 'Octobre';
|
|
||||||
case 11:
|
|
||||||
return 'Novembre';
|
|
||||||
case 12:
|
|
||||||
return 'Décembre';
|
|
||||||
default:
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String _getMonthYearString(DateTime weekStart, DateTime weekEnd) {
|
|
||||||
if (weekStart.month == weekEnd.month) {
|
|
||||||
return '${_getMonthName(weekStart.month)} ${weekStart.year}';
|
|
||||||
} else {
|
|
||||||
return '${_getMonthName(weekStart.month)} - ${_getMonthName(weekEnd.month)} ${weekEnd.year}';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PositionedEvent {
|
|
||||||
final EventModel event;
|
|
||||||
final DateTime start;
|
|
||||||
final DateTime end;
|
|
||||||
_PositionedEvent(this.event, this.start, this.end);
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PositionedEventWithColumn extends _PositionedEvent {
|
|
||||||
final int column;
|
|
||||||
final int totalColumns;
|
|
||||||
_PositionedEventWithColumn(EventModel event, DateTime start, DateTime end,
|
|
||||||
this.column, this.totalColumns)
|
|
||||||
: super(event, start, end);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<_PositionedEventWithColumn> _assignColumns(List<_PositionedEvent> events) {
|
|
||||||
// Algorithme simple :
|
|
||||||
// - Trier par heure de début
|
|
||||||
// - Pour chaque événement, trouver la première colonne libre
|
|
||||||
// - Attribuer le nombre total de colonnes pour le groupe de chevauchement
|
|
||||||
events.sort((a, b) => a.start.compareTo(b.start));
|
|
||||||
List<_PositionedEventWithColumn> result = [];
|
|
||||||
List<List<_PositionedEventWithColumn>> columns = [];
|
|
||||||
for (final e in events) {
|
|
||||||
bool placed = false;
|
|
||||||
for (int col = 0; col < columns.length; col++) {
|
|
||||||
if (columns[col].isEmpty || !(_overlap(columns[col].last, e))) {
|
|
||||||
columns[col]
|
|
||||||
.add(_PositionedEventWithColumn(e.event, e.start, e.end, col, 0));
|
|
||||||
placed = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!placed) {
|
|
||||||
columns.add([
|
|
||||||
_PositionedEventWithColumn(e.event, e.start, e.end, columns.length, 0)
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Mettre à jour le nombre total de colonnes pour chaque événement
|
|
||||||
int totalCols = columns.length;
|
|
||||||
for (final col in columns) {
|
|
||||||
for (final e in col) {
|
|
||||||
result.add(_PositionedEventWithColumn(
|
|
||||||
e.event, e.start, e.end, e.column, totalCols));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _overlap(_PositionedEvent a, _PositionedEvent b) {
|
|
||||||
return a.end.isAfter(b.start) && a.start.isBefore(b.end);
|
|
||||||
}
|
|
||||||
|
@ -11,7 +11,7 @@ import 'package:em2rp/models/role_model.dart';
|
|||||||
import 'package:em2rp/widgets/custom_app_bar.dart';
|
import 'package:em2rp/widgets/custom_app_bar.dart';
|
||||||
|
|
||||||
class UserManagementPage extends StatefulWidget {
|
class UserManagementPage extends StatefulWidget {
|
||||||
const UserManagementPage({Key? key}) : super(key: key);
|
const UserManagementPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<UserManagementPage> createState() => _UserManagementPageState();
|
State<UserManagementPage> createState() => _UserManagementPageState();
|
||||||
@ -21,8 +21,11 @@ class _UserManagementPageState extends State<UserManagementPage> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
Future.microtask(
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
() => Provider.of<UsersProvider>(context, listen: false).fetchUsers());
|
if (mounted) {
|
||||||
|
Provider.of<UsersProvider>(context, listen: false).fetchUsers();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -242,17 +245,21 @@ class _UserManagementPageState extends State<UserManagementPage> {
|
|||||||
profilePhotoUrl: '',
|
profilePhotoUrl: '',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||||
await Provider.of<UsersProvider>(context, listen: false)
|
await Provider.of<UsersProvider>(context, listen: false)
|
||||||
.createUserWithEmailInvite(newUser);
|
.createUserWithEmailInvite(newUser);
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
scaffoldMessenger.showSnackBar(
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text('Invitation envoyée avec succès'),
|
content: Text('Invitation envoyée avec succès'),
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text(
|
||||||
@ -261,6 +268,7 @@ class _UserManagementPageState extends State<UserManagementPage> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppColors.rouge,
|
backgroundColor: AppColors.rouge,
|
||||||
|
238
em2rp/lib/views/widgets/calendar_widgets/month_view.dart
Normal file
238
em2rp/lib/views/widgets/calendar_widgets/month_view.dart
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:table_calendar/table_calendar.dart';
|
||||||
|
import 'package:em2rp/utils/colors.dart';
|
||||||
|
import 'package:em2rp/models/event_model.dart';
|
||||||
|
import 'package:em2rp/utils/calendar_utils.dart';
|
||||||
|
|
||||||
|
class MonthView extends StatelessWidget {
|
||||||
|
final DateTime focusedDay;
|
||||||
|
final DateTime? selectedDay;
|
||||||
|
final CalendarFormat calendarFormat;
|
||||||
|
final Function(DateTime, DateTime) onDaySelected;
|
||||||
|
final Function(CalendarFormat) onFormatChanged;
|
||||||
|
final Function(DateTime) onPageChanged;
|
||||||
|
final List<EventModel> events;
|
||||||
|
final Function(EventModel) onEventSelected;
|
||||||
|
|
||||||
|
const MonthView({
|
||||||
|
super.key,
|
||||||
|
required this.focusedDay,
|
||||||
|
required this.selectedDay,
|
||||||
|
required this.calendarFormat,
|
||||||
|
required this.onDaySelected,
|
||||||
|
required this.onFormatChanged,
|
||||||
|
required this.onPageChanged,
|
||||||
|
required this.events,
|
||||||
|
required this.onEventSelected,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
final rowHeight = (constraints.maxHeight - 100) / 6;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
height: constraints.maxHeight,
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: TableCalendar(
|
||||||
|
firstDay: DateTime.utc(2020, 1, 1),
|
||||||
|
lastDay: DateTime.utc(2030, 12, 31),
|
||||||
|
focusedDay: focusedDay,
|
||||||
|
calendarFormat: calendarFormat,
|
||||||
|
startingDayOfWeek: StartingDayOfWeek.monday,
|
||||||
|
locale: 'fr_FR',
|
||||||
|
availableCalendarFormats: const {
|
||||||
|
CalendarFormat.month: 'Mois',
|
||||||
|
CalendarFormat.week: 'Semaine',
|
||||||
|
},
|
||||||
|
selectedDayPredicate: (day) => isSameDay(selectedDay, day),
|
||||||
|
onDaySelected: onDaySelected,
|
||||||
|
onFormatChanged: onFormatChanged,
|
||||||
|
onPageChanged: onPageChanged,
|
||||||
|
calendarStyle: _buildCalendarStyle(),
|
||||||
|
rowHeight: rowHeight,
|
||||||
|
headerStyle: _buildHeaderStyle(),
|
||||||
|
calendarBuilders: _buildCalendarBuilders(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
CalendarStyle _buildCalendarStyle() {
|
||||||
|
return CalendarStyle(
|
||||||
|
defaultDecoration: BoxDecoration(
|
||||||
|
border: Border.all(color: Colors.grey.shade300),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
selectedDecoration: BoxDecoration(
|
||||||
|
color: AppColors.rouge,
|
||||||
|
border: Border.all(color: AppColors.rouge),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
todayDecoration: BoxDecoration(
|
||||||
|
color: AppColors.rouge.withAlpha(26),
|
||||||
|
border: Border.all(color: AppColors.rouge),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
outsideDecoration: BoxDecoration(
|
||||||
|
border: Border.all(color: Colors.grey.shade300),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
outsideDaysVisible: false,
|
||||||
|
cellMargin: EdgeInsets.zero,
|
||||||
|
cellPadding: EdgeInsets.zero,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
HeaderStyle _buildHeaderStyle() {
|
||||||
|
return HeaderStyle(
|
||||||
|
formatButtonVisible: true,
|
||||||
|
titleCentered: true,
|
||||||
|
formatButtonShowsNext: false,
|
||||||
|
formatButtonDecoration: BoxDecoration(
|
||||||
|
color: AppColors.rouge,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
formatButtonTextStyle: const TextStyle(color: Colors.white),
|
||||||
|
leftChevronIcon: const Icon(Icons.chevron_left, color: AppColors.rouge),
|
||||||
|
rightChevronIcon: const Icon(Icons.chevron_right, color: AppColors.rouge),
|
||||||
|
headerPadding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
titleTextStyle: const TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
CalendarBuilders _buildCalendarBuilders() {
|
||||||
|
return CalendarBuilders(
|
||||||
|
dowBuilder: (context, day) {
|
||||||
|
return Center(
|
||||||
|
child: Text(
|
||||||
|
CalendarUtils.getShortDayName(day.weekday),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
defaultBuilder: (context, day, focusedDay) {
|
||||||
|
return _buildDayCell(day, false);
|
||||||
|
},
|
||||||
|
selectedBuilder: (context, day, focusedDay) {
|
||||||
|
return _buildDayCell(day, true);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDayCell(DateTime day, bool isSelected) {
|
||||||
|
final dayEvents = CalendarUtils.getEventsForDay(day, events);
|
||||||
|
final textColor = isSelected ? Colors.white : null;
|
||||||
|
final badgeColor = isSelected ? Colors.white : AppColors.rouge;
|
||||||
|
final badgeTextColor = isSelected ? AppColors.rouge : Colors.white;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.all(4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isSelected ? AppColors.rouge : null,
|
||||||
|
border: Border.all(
|
||||||
|
color: isSelected ? AppColors.rouge : Colors.grey.shade300,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Positioned(
|
||||||
|
top: 4,
|
||||||
|
left: 4,
|
||||||
|
child: Text(
|
||||||
|
day.day.toString(),
|
||||||
|
style: TextStyle(color: textColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (dayEvents.isNotEmpty)
|
||||||
|
Positioned(
|
||||||
|
top: 4,
|
||||||
|
right: 4,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: badgeColor,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
dayEvents.length.toString(),
|
||||||
|
style: TextStyle(
|
||||||
|
color: badgeTextColor,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (dayEvents.isNotEmpty)
|
||||||
|
Positioned(
|
||||||
|
bottom: 2,
|
||||||
|
left: 2,
|
||||||
|
right: 2,
|
||||||
|
top: 28,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: dayEvents
|
||||||
|
.map((event) => _buildEventItem(event, isSelected, day))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildEventItem(
|
||||||
|
EventModel event, bool isSelected, DateTime currentDay) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
onDaySelected(currentDay, currentDay);
|
||||||
|
onEventSelected(event);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 2),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isSelected
|
||||||
|
? Colors.white.withAlpha(51)
|
||||||
|
: AppColors.rouge.withAlpha(26),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
event.name,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: isSelected ? Colors.white : AppColors.rouge,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
if (CalendarUtils.isMultiDayEvent(event))
|
||||||
|
Text(
|
||||||
|
'Jour ${CalendarUtils.calculateDayNumber(event.startDateTime, event.startDateTime)}/${CalendarUtils.calculateTotalDays(event)}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
color: isSelected ? Colors.white : AppColors.rouge,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
383
em2rp/lib/views/widgets/calendar_widgets/week_view.dart
Normal file
383
em2rp/lib/views/widgets/calendar_widgets/week_view.dart
Normal file
@ -0,0 +1,383 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:em2rp/utils/colors.dart';
|
||||||
|
import 'package:em2rp/models/event_model.dart';
|
||||||
|
import 'package:em2rp/utils/calendar_utils.dart';
|
||||||
|
|
||||||
|
class WeekView extends StatelessWidget {
|
||||||
|
final DateTime focusedDay;
|
||||||
|
final List<EventModel> events;
|
||||||
|
final Function(int) onWeekChange;
|
||||||
|
final Function(EventModel) onEventSelected;
|
||||||
|
final Function() onSwitchToMonth;
|
||||||
|
|
||||||
|
const WeekView({
|
||||||
|
super.key,
|
||||||
|
required this.focusedDay,
|
||||||
|
required this.events,
|
||||||
|
required this.onWeekChange,
|
||||||
|
required this.onEventSelected,
|
||||||
|
required this.onSwitchToMonth,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final weekStart =
|
||||||
|
focusedDay.subtract(Duration(days: focusedDay.weekday - 1));
|
||||||
|
final weekEnd = weekStart.add(const Duration(days: 6));
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
_buildWeekHeader(weekStart, weekEnd),
|
||||||
|
Expanded(
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
final availableHeight = constraints.maxHeight - 80;
|
||||||
|
final hourHeight = availableHeight / 24;
|
||||||
|
final dayWidth = (constraints.maxWidth - 50) / 7;
|
||||||
|
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: SizedBox(
|
||||||
|
height: 24 * hourHeight,
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildHourColumn(hourHeight),
|
||||||
|
Expanded(
|
||||||
|
child: _buildWeekGrid(
|
||||||
|
weekStart,
|
||||||
|
hourHeight,
|
||||||
|
dayWidth,
|
||||||
|
constraints,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildWeekHeader(DateTime weekStart, DateTime weekEnd) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.chevron_left),
|
||||||
|
onPressed: () => onWeekChange(-1),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
CalendarUtils.getMonthYearString(weekStart, weekEnd),
|
||||||
|
style:
|
||||||
|
const TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: onSwitchToMonth,
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
backgroundColor: AppColors.rouge,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12, vertical: 8),
|
||||||
|
),
|
||||||
|
child: const Text('Semaine'),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.chevron_right),
|
||||||
|
onPressed: () => onWeekChange(1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_buildDaysHeader(weekStart),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDaysHeader(DateTime weekStart) {
|
||||||
|
return SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 50,
|
||||||
|
color: Colors.transparent,
|
||||||
|
),
|
||||||
|
...List.generate(7, (index) {
|
||||||
|
final day = weekStart.add(Duration(days: index));
|
||||||
|
return Expanded(
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
right: BorderSide(
|
||||||
|
color:
|
||||||
|
index < 6 ? Colors.grey.shade300 : Colors.transparent,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
CalendarUtils.getShortDayName(day.weekday),
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'${day.day}',
|
||||||
|
style: const TextStyle(fontSize: 13),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (CalendarUtils.getEventsForDay(day, events).isNotEmpty)
|
||||||
|
Positioned(
|
||||||
|
top: 4,
|
||||||
|
right: 4,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 6, vertical: 2),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.rouge,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
CalendarUtils.getEventsForDay(day, events)
|
||||||
|
.length
|
||||||
|
.toString(),
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHourColumn(double hourHeight) {
|
||||||
|
return Column(
|
||||||
|
children: List.generate(24, (index) {
|
||||||
|
return Container(
|
||||||
|
width: 50,
|
||||||
|
height: hourHeight,
|
||||||
|
alignment: Alignment.topRight,
|
||||||
|
padding: const EdgeInsets.only(right: 4),
|
||||||
|
child: Text(
|
||||||
|
'${index.toString().padLeft(2, '0')}:00',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey.shade600,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildWeekGrid(
|
||||||
|
DateTime weekStart,
|
||||||
|
double hourHeight,
|
||||||
|
double dayWidth,
|
||||||
|
BoxConstraints constraints,
|
||||||
|
) {
|
||||||
|
final eventsByDay = _prepareEventsByDay(weekStart);
|
||||||
|
final eventsWithColumnsByDay = _assignColumnsToEvents(eventsByDay);
|
||||||
|
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
// Lignes horizontales
|
||||||
|
Column(
|
||||||
|
children: List.generate(24, (index) {
|
||||||
|
return Container(
|
||||||
|
height: hourHeight,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
color: Colors.grey.shade300,
|
||||||
|
width: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
// Bordures verticales entre jours
|
||||||
|
Positioned.fill(
|
||||||
|
child: Row(
|
||||||
|
children: List.generate(7, (i) {
|
||||||
|
return Container(
|
||||||
|
width: dayWidth,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
right: BorderSide(
|
||||||
|
color: i < 6 ? Colors.grey.shade300 : Colors.transparent,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Événements
|
||||||
|
...List.generate(7, (dayIdx) {
|
||||||
|
final dayEvents = eventsWithColumnsByDay[dayIdx];
|
||||||
|
return Stack(
|
||||||
|
children: dayEvents.map((e) {
|
||||||
|
final startHour = e.start.hour + e.start.minute / 60;
|
||||||
|
final endHour = e.end.hour + e.end.minute / 60;
|
||||||
|
final duration = endHour - startHour;
|
||||||
|
final width = dayWidth / e.totalColumns;
|
||||||
|
|
||||||
|
return Positioned(
|
||||||
|
left: dayIdx * dayWidth + e.column * width,
|
||||||
|
top: startHour * hourHeight,
|
||||||
|
width: width,
|
||||||
|
height: duration * hourHeight,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => onEventSelected(e.event),
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.all(2),
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.rouge.withAlpha(26),
|
||||||
|
border: Border.all(color: AppColors.rouge),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
e.event.name,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: AppColors.rouge,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
if (CalendarUtils.isMultiDayEvent(e.event))
|
||||||
|
Text(
|
||||||
|
'Jour ${CalendarUtils.calculateDayNumber(e.event.startDateTime, weekStart.add(Duration(days: dayIdx)))}/${CalendarUtils.calculateTotalDays(e.event)}',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: AppColors.rouge,
|
||||||
|
fontSize: 10,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<List<_PositionedEvent>> _prepareEventsByDay(DateTime weekStart) {
|
||||||
|
List<List<_PositionedEvent>> eventsByDay = List.generate(7, (i) => []);
|
||||||
|
|
||||||
|
for (final event in events) {
|
||||||
|
for (int i = 0; i < 7; i++) {
|
||||||
|
final day = weekStart.add(Duration(days: i));
|
||||||
|
final dayStart = DateTime(day.year, day.month, day.day, 0, 0);
|
||||||
|
final dayEnd = DateTime(day.year, day.month, day.day, 23, 59, 59);
|
||||||
|
|
||||||
|
if (!(event.endDateTime.isBefore(dayStart) ||
|
||||||
|
event.startDateTime.isAfter(dayEnd))) {
|
||||||
|
final start = event.startDateTime.isBefore(dayStart)
|
||||||
|
? dayStart
|
||||||
|
: event.startDateTime;
|
||||||
|
final end =
|
||||||
|
event.endDateTime.isAfter(dayEnd) ? dayEnd : event.endDateTime;
|
||||||
|
eventsByDay[i].add(_PositionedEvent(event, start, end));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return eventsByDay;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<List<_PositionedEventWithColumn>> _assignColumnsToEvents(
|
||||||
|
List<List<_PositionedEvent>> eventsByDay) {
|
||||||
|
return eventsByDay.map((dayEvents) {
|
||||||
|
dayEvents.sort((a, b) => a.start.compareTo(b.start));
|
||||||
|
List<_PositionedEventWithColumn> result = [];
|
||||||
|
List<List<_PositionedEventWithColumn>> columns = [];
|
||||||
|
|
||||||
|
for (final e in dayEvents) {
|
||||||
|
bool placed = false;
|
||||||
|
for (int col = 0; col < columns.length; col++) {
|
||||||
|
if (columns[col].isEmpty || !_overlap(columns[col].last, e)) {
|
||||||
|
columns[col].add(
|
||||||
|
_PositionedEventWithColumn(e.event, e.start, e.end, col, 0));
|
||||||
|
placed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!placed) {
|
||||||
|
columns.add([
|
||||||
|
_PositionedEventWithColumn(
|
||||||
|
e.event, e.start, e.end, columns.length, 0)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int totalCols = columns.length;
|
||||||
|
for (final col in columns) {
|
||||||
|
for (final e in col) {
|
||||||
|
result.add(_PositionedEventWithColumn(
|
||||||
|
e.event, e.start, e.end, e.column, totalCols));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _overlap(_PositionedEvent a, _PositionedEvent b) {
|
||||||
|
return a.end.isAfter(b.start) && a.start.isBefore(b.end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PositionedEvent {
|
||||||
|
final EventModel event;
|
||||||
|
final DateTime start;
|
||||||
|
final DateTime end;
|
||||||
|
_PositionedEvent(this.event, this.start, this.end);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PositionedEventWithColumn extends _PositionedEvent {
|
||||||
|
final int column;
|
||||||
|
final int totalColumns;
|
||||||
|
_PositionedEventWithColumn(
|
||||||
|
super.event, super.start, super.end, this.column, this.totalColumns);
|
||||||
|
}
|
@ -7,7 +7,7 @@ class BigLeftImageWidget extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
color: AppColors.gris.withOpacity(0.1),
|
color: AppColors.gris.withAlpha(26),
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.zero,
|
borderRadius: BorderRadius.zero,
|
||||||
child: Image.asset(
|
child: Image.asset(
|
||||||
|
@ -15,12 +15,12 @@ class _ProfilePictureSelectorState extends State<ProfilePictureSelector> {
|
|||||||
bool _isHovering = false;
|
bool _isHovering = false;
|
||||||
|
|
||||||
Future<void> _pickAndUploadImage() async {
|
Future<void> _pickAndUploadImage() async {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
final provider = Provider.of<LocalUserProvider>(context, listen: false);
|
||||||
final ImagePicker picker = ImagePicker();
|
final ImagePicker picker = ImagePicker();
|
||||||
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
|
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
|
||||||
if (image != null) {
|
if (image != null && context.mounted) {
|
||||||
// Envoie l'image au provider
|
await provider.changeProfilePicture(image);
|
||||||
await Provider.of<LocalUserProvider>(context, listen: false)
|
|
||||||
.changeProfilePicture(image);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,9 +13,9 @@ class MainDrawer extends StatelessWidget {
|
|||||||
final String currentPage;
|
final String currentPage;
|
||||||
|
|
||||||
const MainDrawer({
|
const MainDrawer({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.currentPage,
|
required this.currentPage,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -33,7 +33,7 @@ class MainDrawer extends StatelessWidget {
|
|||||||
image: const AssetImage('assets/EM2_NsurB.jpg'),
|
image: const AssetImage('assets/EM2_NsurB.jpg'),
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
colorFilter: ColorFilter.mode(
|
colorFilter: ColorFilter.mode(
|
||||||
AppColors.noir.withOpacity(0.4),
|
AppColors.noir.withAlpha(102),
|
||||||
BlendMode.darken,
|
BlendMode.darken,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -88,7 +88,8 @@ class MainDrawer extends StatelessWidget {
|
|||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
Navigator.pushReplacement(
|
Navigator.pushReplacement(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (context) => const CalendarPage()),
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const CalendarPage()),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -10,11 +10,11 @@ class UserCard extends StatelessWidget {
|
|||||||
static const double _desktopMaxWidth = 280;
|
static const double _desktopMaxWidth = 280;
|
||||||
|
|
||||||
const UserCard({
|
const UserCard({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.user,
|
required this.user,
|
||||||
required this.onEdit,
|
required this.onEdit,
|
||||||
required this.onDelete,
|
required this.onDelete,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -3,25 +3,33 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:em2rp/providers/local_user_provider.dart';
|
import 'package:em2rp/providers/local_user_provider.dart';
|
||||||
import 'package:em2rp/utils/colors.dart';
|
import 'package:em2rp/utils/colors.dart';
|
||||||
|
|
||||||
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
|
class CustomAppBar extends StatefulWidget implements PreferredSizeWidget {
|
||||||
final String title;
|
final String title;
|
||||||
final List<Widget>? actions;
|
final List<Widget>? actions;
|
||||||
final bool showLogoutButton;
|
final bool showLogoutButton;
|
||||||
|
|
||||||
const CustomAppBar({
|
const CustomAppBar({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.title,
|
required this.title,
|
||||||
this.actions,
|
this.actions,
|
||||||
this.showLogoutButton = true,
|
this.showLogoutButton = true,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CustomAppBar> createState() => _CustomAppBarState();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CustomAppBarState extends State<CustomAppBar> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AppBar(
|
return AppBar(
|
||||||
title: Text(title),
|
title: Text(widget.title),
|
||||||
backgroundColor: AppColors.rouge,
|
backgroundColor: AppColors.rouge,
|
||||||
actions: [
|
actions: [
|
||||||
if (showLogoutButton)
|
if (widget.showLogoutButton)
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.logout, color: AppColors.blanc),
|
icon: const Icon(Icons.logout, color: AppColors.blanc),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
@ -45,21 +53,19 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (shouldLogout == true) {
|
if (shouldLogout == true && context.mounted) {
|
||||||
// Déconnexion
|
// Déconnexion
|
||||||
await Provider.of<LocalUserProvider>(context, listen: false)
|
final provider =
|
||||||
.signOut();
|
Provider.of<LocalUserProvider>(context, listen: false);
|
||||||
|
await provider.signOut();
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
Navigator.of(context).pushReplacementNamed('/login');
|
Navigator.of(context).pushReplacementNamed('/login');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (actions != null) ...actions!,
|
if (widget.actions != null) ...widget.actions!,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
|
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,9 @@ class EventDetails extends StatelessWidget {
|
|||||||
final EventModel event;
|
final EventModel event;
|
||||||
|
|
||||||
const EventDetails({
|
const EventDetails({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.event,
|
required this.event,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
Reference in New Issue
Block a user