Refacto et clean

This commit is contained in:
2025-05-18 20:34:57 +02:00
parent 62c6125d8c
commit 6adc90ecfe
13 changed files with 830 additions and 792 deletions

View 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);
}