Refacto et clean
This commit is contained in:
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);
|
||||
}
|
Reference in New Issue
Block a user