From d39475f5182fa2dfac32ae453cd54f332a00ec6e Mon Sep 17 00:00:00 2001 From: Adrian Mong Date: Fri, 25 Oct 2024 11:24:55 +0300 Subject: [PATCH] Support timezone and hour limitation --- lib/src/components/_internal_components.dart | 31 +++++++++----- lib/src/day_view/_internal_day_view_page.dart | 1 + lib/src/day_view/day_view.dart | 42 ++++++++++++++++--- lib/src/event_arrangers/event_arrangers.dart | 1 + .../event_arrangers/merge_event_arranger.dart | 15 ++++++- .../event_arrangers/side_event_arranger.dart | 22 +++++++--- lib/src/helpers/time_zone.dart | 24 +++++++++++ .../week_view/_internal_week_view_page.dart | 1 + lib/src/week_view/week_view.dart | 42 ++++++++++++++++--- 9 files changed, 152 insertions(+), 27 deletions(-) create mode 100644 lib/src/helpers/time_zone.dart diff --git a/lib/src/components/_internal_components.dart b/lib/src/components/_internal_components.dart index d678657a..8e7fc40e 100644 --- a/lib/src/components/_internal_components.dart +++ b/lib/src/components/_internal_components.dart @@ -37,15 +37,19 @@ class LiveTimeIndicator extends StatefulWidget { /// Defines height occupied by one minute. final double heightPerMinute; + // Start time to display + final TimeOfDay? startTime; + /// Widget to display tile line according to current time. - const LiveTimeIndicator( - {Key? key, - required this.width, - required this.height, - required this.timeLineWidth, - required this.liveTimeIndicatorSettings, - required this.heightPerMinute}) - : super(key: key); + const LiveTimeIndicator({ + Key? key, + required this.width, + required this.height, + required this.timeLineWidth, + required this.liveTimeIndicatorSettings, + required this.heightPerMinute, + this.startTime, + }) : super(key: key); @override _LiveTimeIndicatorState createState() => _LiveTimeIndicatorState(); @@ -81,6 +85,12 @@ class _LiveTimeIndicatorState extends State { @override Widget build(BuildContext context) { + int currentMinutes = _currentTime.getTotalMinutes; + + if (widget.startTime != null) { + currentMinutes -= widget.startTime!.getTotalMinutes; + } + return CustomPaint( size: Size(widget.width, widget.height), painter: CurrentTimeLinePainter( @@ -88,7 +98,7 @@ class _LiveTimeIndicatorState extends State { height: widget.liveTimeIndicatorSettings.height, offset: Offset( widget.timeLineWidth + widget.liveTimeIndicatorSettings.offset, - _currentTime.getTotalMinutes * widget.heightPerMinute, + currentMinutes * widget.heightPerMinute, ), ), ); @@ -148,7 +158,7 @@ class TimeLine extends StatelessWidget { @override Widget build(BuildContext context) { - int? initialHour = startTime != null ? startTime!.hour : 1; + int? initialHour = startTime != null ? startTime!.hour : 0; int? hoursADay = endTime != null ? endTime!.hour : Constants.hoursADay; int totalHours = hoursADay - initialHour; return ConstrainedBox( @@ -289,6 +299,7 @@ class EventGenerator extends StatelessWidget { heightPerMinute: heightPerMinute, ); +// position tile return List.generate(events.length, (index) { return Positioned( top: events[index].top, diff --git a/lib/src/day_view/_internal_day_view_page.dart b/lib/src/day_view/_internal_day_view_page.dart index 7541d048..21b17b37 100644 --- a/lib/src/day_view/_internal_day_view_page.dart +++ b/lib/src/day_view/_internal_day_view_page.dart @@ -271,6 +271,7 @@ class InternalDayViewPage extends StatelessWidget { height: height, heightPerMinute: heightPerMinute, timeLineWidth: timeLineWidth, + startTime: startTime, ), ), ], diff --git a/lib/src/day_view/day_view.dart b/lib/src/day_view/day_view.dart index 81bb9074..147fd8fe 100644 --- a/lib/src/day_view/day_view.dart +++ b/lib/src/day_view/day_view.dart @@ -17,6 +17,7 @@ import '../enumerations.dart'; import '../event_arrangers/event_arrangers.dart'; import '../event_controller.dart'; import '../extensions.dart'; +import '../helpers/time_zone.dart'; import '../modals.dart'; import '../painters.dart'; import '../style/header_style.dart'; @@ -333,10 +334,18 @@ class DayViewState extends State> { final _scrollConfiguration = EventScrollConfiguration(); + late TimeOfDay? startTime; + + late TimeOfDay? endTime; + + int timeZoneOffset = 0; + @override void initState() { super.initState(); + _updateTimeToCurrentTimeZone(); + _reloadCallback = _reload; _setDateRange(); @@ -349,10 +358,29 @@ class DayViewState extends State> { initialScrollOffset: widget.scrollOffset ?? widget.startDuration.inMinutes * widget.heightPerMinute); _pageController = PageController(initialPage: _currentIndex); - _eventArranger = widget.eventArranger ?? SideEventArranger(); + _eventArranger = widget.eventArranger ?? + SideEventArranger(startTime: widget.startTime); _assignBuilders(); } + void _updateTimeToCurrentTimeZone() { + final int currentTimeZoneOffset = widget.locationName != null + ? TimeZoneHelper.getTimeZoneOffset(widget.locationName!) + : 0; + final TimeOfDay? finalStartTime = widget.startTime != null + ? TimeZoneHelper.addHour(widget.startTime!, currentTimeZoneOffset) + : null; + final TimeOfDay? finalEndTime = widget.endTime != null + ? TimeZoneHelper.addHour(widget.endTime!, currentTimeZoneOffset) + : null; + + setState(() { + timeZoneOffset = currentTimeZoneOffset; + startTime = finalStartTime; + endTime = finalEndTime; + }); + } + @override void didChangeDependencies() { super.didChangeDependencies(); @@ -376,6 +404,9 @@ class DayViewState extends State> { @override void didUpdateWidget(DayView oldWidget) { super.didUpdateWidget(oldWidget); + + _updateTimeToCurrentTimeZone(); + // Update controller. final newController = widget.controller ?? CalendarControllerProvider.of(context).controller; @@ -395,7 +426,8 @@ class DayViewState extends State> { _pageController.jumpToPage(_currentIndex); } - _eventArranger = widget.eventArranger ?? SideEventArranger(); + _eventArranger = widget.eventArranger ?? + SideEventArranger(startTime: widget.startTime); // Update heights. _calculateHeights(); @@ -479,8 +511,8 @@ class DayViewState extends State> { emulateVerticalOffsetBy: widget.emulateVerticalOffsetBy, locationName: widget.locationName, - startTime: widget.startTime, - endTime: widget.endTime, + startTime: startTime, + endTime: endTime, ), ); }, @@ -639,7 +671,7 @@ class DayViewState extends State> { required MinuteSlotSize minuteSlotSize, }) { int hoursADay = _getHoursADay().toInt(); - int startHour = widget.startTime != null ? widget.startTime!.hour : 0; + int startHour = startTime != null ? startTime!.hour : 0; final heightPerSlot = minuteSlotSize.minutes * heightPerMinute; final slots = (hoursADay * 60) ~/ minuteSlotSize.minutes; diff --git a/lib/src/event_arrangers/event_arrangers.dart b/lib/src/event_arrangers/event_arrangers.dart index 001d304a..71fbf3c4 100644 --- a/lib/src/event_arrangers/event_arrangers.dart +++ b/lib/src/event_arrangers/event_arrangers.dart @@ -5,6 +5,7 @@ import 'dart:math' as math; import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; import '../calendar_event_data.dart'; import '../constants.dart'; diff --git a/lib/src/event_arrangers/merge_event_arranger.dart b/lib/src/event_arrangers/merge_event_arranger.dart index 0b8a7a6f..f597ca35 100644 --- a/lib/src/event_arrangers/merge_event_arranger.dart +++ b/lib/src/event_arrangers/merge_event_arranger.dart @@ -10,6 +10,7 @@ class MergeEventArranger extends EventArranger { /// [OrganizedCalendarEventData.events] will gives /// list of all the combined events. const MergeEventArranger({ + this.startTime, this.includeEdges = true, }); @@ -21,6 +22,9 @@ class MergeEventArranger extends EventArranger { /// final bool includeEdges; + // Start time to display + final TimeOfDay? startTime; + /// {@macro event_arranger_arrange_method_doc} /// /// Make sure that all the events that are passed in [events], must be in @@ -37,6 +41,13 @@ class MergeEventArranger extends EventArranger { // final arrangedEvents = >[]; + int startMinutes = 0; + + if (startTime != null) { + // Subtract start time to calculate correct tile position + startMinutes = startTime!.getTotalMinutes * -1; + } + for (final event in events) { // Checks if an event has valid start and end time. if (event.startTime == null || @@ -62,10 +73,10 @@ class MergeEventArranger extends EventArranger { final startTime = event.startTime!; final endTime = event.endTime!; - final eventStart = startTime.getTotalMinutes; + final eventStart = startTime.getTotalMinutes + startMinutes; final eventEnd = endTime.getTotalMinutes == 0 ? Constants.minutesADay - : endTime.getTotalMinutes; + : endTime.getTotalMinutes + startMinutes; final arrangeEventLen = arrangedEvents.length; diff --git a/lib/src/event_arrangers/side_event_arranger.dart b/lib/src/event_arrangers/side_event_arranger.dart index d23e2e83..712466c8 100644 --- a/lib/src/event_arrangers/side_event_arranger.dart +++ b/lib/src/event_arrangers/side_event_arranger.dart @@ -8,6 +8,7 @@ class SideEventArranger extends EventArranger { /// This class will provide method that will arrange /// all the events side by side. const SideEventArranger({ + this.startTime, this.includeEdges = false, }); @@ -19,6 +20,9 @@ class SideEventArranger extends EventArranger { /// final bool includeEdges; + // Start time to display + final TimeOfDay? startTime; + /// {@macro event_arranger_arrange_method_doc} /// /// Make sure that all the events that are passed in [events], must be in @@ -30,9 +34,9 @@ class SideEventArranger extends EventArranger { required double width, required double heightPerMinute, }) { - final mergedEvents = MergeEventArranger( - includeEdges: includeEdges, - ).arrange( + final mergedEvents = + MergeEventArranger(includeEdges: includeEdges, startTime: startTime) + .arrange( events: events, height: height, width: width, @@ -83,6 +87,13 @@ class SideEventArranger extends EventArranger { final slotWidth = width / column; + int startMinutes = 0; + + if (startTime != null) { + // Subtract start time to calculate correct tile position + startMinutes = startTime!.getTotalMinutes * -1; + } + for (final sideEvent in sideEventData) { if (sideEvent.event.startTime == null || sideEvent.event.endTime == null) { @@ -103,12 +114,13 @@ class SideEventArranger extends EventArranger { final bottom = height - (endTime.getTotalMinutes == 0 ? Constants.minutesADay - : endTime.getTotalMinutes) * + : endTime.getTotalMinutes + startMinutes) * heightPerMinute; + // ou ici arrangedEvents.add(OrganizedCalendarEventData( left: slotWidth * (sideEvent.column - 1), right: slotWidth * (column - sideEvent.column), - top: startTime.getTotalMinutes * heightPerMinute, + top: (startTime.getTotalMinutes + startMinutes) * heightPerMinute, bottom: bottom, startDuration: startTime, endDuration: endTime, diff --git a/lib/src/helpers/time_zone.dart b/lib/src/helpers/time_zone.dart new file mode 100644 index 00000000..3739fa19 --- /dev/null +++ b/lib/src/helpers/time_zone.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:timezone/standalone.dart' as tz; +import 'package:timezone/timezone.dart'; + +class TimeZoneHelper { + static DateTime? getTzDateFromDateTime( + DateTime? dateTime, String locationName) { + if (dateTime == null) { + return null; + } + final Location location = tz.getLocation(locationName); + return TZDateTime.from(dateTime, location); + } + + static int getTimeZoneOffset(String location) { + final now = DateTime.now(); + return now.timeZoneOffset.inHours - + getTzDateFromDateTime(now, location)!.timeZoneOffset.inHours; + } + + static TimeOfDay addHour(TimeOfDay time, int hour) { + return TimeOfDay(hour: time.hour + hour, minute: time.minute); + } +} diff --git a/lib/src/week_view/_internal_week_view_page.dart b/lib/src/week_view/_internal_week_view_page.dart index bd34c7a7..48c73b36 100644 --- a/lib/src/week_view/_internal_week_view_page.dart +++ b/lib/src/week_view/_internal_week_view_page.dart @@ -308,6 +308,7 @@ class InternalWeekViewPage extends StatelessWidget { height: height, heightPerMinute: heightPerMinute, timeLineWidth: timeLineWidth, + startTime: startTime, ), Align( alignment: Alignment.centerRight, diff --git a/lib/src/week_view/week_view.dart b/lib/src/week_view/week_view.dart index 3262816e..0f5ea895 100644 --- a/lib/src/week_view/week_view.dart +++ b/lib/src/week_view/week_view.dart @@ -15,6 +15,7 @@ import '../enumerations.dart'; import '../event_arrangers/event_arrangers.dart'; import '../event_controller.dart'; import '../extensions.dart'; +import '../helpers/time_zone.dart'; import '../modals.dart'; import '../painters.dart'; import '../style/header_style.dart'; @@ -343,10 +344,18 @@ class WeekViewState extends State> { final _scrollConfiguration = EventScrollConfiguration(); + late TimeOfDay? startTime; + + late TimeOfDay? endTime; + + int timeZoneOffset = 0; + @override void initState() { super.initState(); + _updateTimeToCurrentTimeZone(); + _reloadCallback = _reload; _setWeekDays(); @@ -360,11 +369,30 @@ class WeekViewState extends State> { _scrollController = ScrollController(initialScrollOffset: widget.scrollOffset); _pageController = PageController(initialPage: _currentIndex); - _eventArranger = widget.eventArranger ?? SideEventArranger(); + _eventArranger = widget.eventArranger ?? + SideEventArranger(startTime: widget.startTime); _assignBuilders(); } + void _updateTimeToCurrentTimeZone() { + final int currentTimeZoneOffset = widget.locationName != null + ? TimeZoneHelper.getTimeZoneOffset(widget.locationName!) + : 0; + final TimeOfDay? finalStartTime = widget.startTime != null + ? TimeZoneHelper.addHour(widget.startTime!, currentTimeZoneOffset) + : null; + final TimeOfDay? finalEndTime = widget.endTime != null + ? TimeZoneHelper.addHour(widget.endTime!, currentTimeZoneOffset) + : null; + + setState(() { + timeZoneOffset = currentTimeZoneOffset; + startTime = finalStartTime; + endTime = finalEndTime; + }); + } + @override void didChangeDependencies() { super.didChangeDependencies(); @@ -388,6 +416,9 @@ class WeekViewState extends State> { @override void didUpdateWidget(WeekView oldWidget) { super.didUpdateWidget(oldWidget); + + _updateTimeToCurrentTimeZone(); + // Update controller. final newController = widget.controller ?? CalendarControllerProvider.of(context).controller; @@ -409,7 +440,8 @@ class WeekViewState extends State> { _pageController.jumpToPage(_currentIndex); } - _eventArranger = widget.eventArranger ?? SideEventArranger(); + _eventArranger = widget.eventArranger ?? + SideEventArranger(startTime: widget.startTime); // Update heights. _calculateHeights(); @@ -504,8 +536,8 @@ class WeekViewState extends State> { widget.emulateVerticalOffsetBy, showWeekDayAtBottom: widget.showWeekDayAtBottom, locationName: widget.locationName, - startTime: widget.startTime, - endTime: widget.endTime, + startTime: startTime, + endTime: endTime, ), ); }, @@ -703,7 +735,7 @@ class WeekViewState extends State> { required MinuteSlotSize minuteSlotSize, }) { int hoursADay = _getHoursADay().toInt(); - int startHour = widget.startTime != null ? widget.startTime!.hour : 0; + int startHour = startTime != null ? startTime!.hour : 0; final heightPerSlot = minuteSlotSize.minutes * heightPerMinute; final slots = (hoursADay * 60) ~/ minuteSlotSize.minutes;