From c003d1b9de565ee525d77a3343417d754e08c222 Mon Sep 17 00:00:00 2001 From: DEVYANK SHAW <47057254+DevyankShaw@users.noreply.github.com> Date: Wed, 21 Feb 2024 22:40:16 +0530 Subject: [PATCH] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20show=20time=20on=20?= =?UTF-8?q?live=20timeline=20(#217)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial implementation to show time on live timeline * Automatic adjust on timeLineWidth increases * Expose two properties of LiveTimeIndicatorSettings * Fixed error produced by dart formatter * Fixed formatting * Formatting fixed * Fixed formatting * Upgraded min dart sdk * Initial implementation to show time on live timeline * Automatic adjust on timeLineWidth increases * Expose two properties of LiveTimeIndicatorSettings * Fixed error produced by dart formatter * Formatting fixed * Fixed formatting * Upgraded min dart sdk * offset corrected --- example/lib/widgets/day_view_widget.dart | 9 ++ example/lib/widgets/week_view_widget.dart | 6 + lib/src/components/_internal_components.dart | 143 +++++++++++++----- lib/src/day_view/_internal_day_view_page.dart | 3 +- lib/src/day_view/day_view.dart | 8 +- lib/src/extensions.dart | 6 + lib/src/modals.dart | 51 +++++++ lib/src/painters.dart | 63 +++++++- .../week_view/_internal_week_view_page.dart | 19 +-- lib/src/week_view/week_view.dart | 7 +- pubspec.yaml | 2 +- 11 files changed, 256 insertions(+), 61 deletions(-) diff --git a/example/lib/widgets/day_view_widget.dart b/example/lib/widgets/day_view_widget.dart index 8a886327..c6225c90 100644 --- a/example/lib/widgets/day_view_widget.dart +++ b/example/lib/widgets/day_view_widget.dart @@ -38,6 +38,15 @@ class DayViewWidget extends StatelessWidget { color: Theme.of(context).dividerColor, lineStyle: LineStyle.dashed, ), + verticalLineOffset: 0, + timeLineWidth: 65, + showLiveTimeLineInAllDays: true, + liveTimeIndicatorSettings: LiveTimeIndicatorSettings( + color: Colors.redAccent, + showBullet: false, + showTime: true, + showTimeBackgroundView: true, + ), ); } diff --git a/example/lib/widgets/week_view_widget.dart b/example/lib/widgets/week_view_widget.dart index adde1e00..4d377023 100644 --- a/example/lib/widgets/week_view_widget.dart +++ b/example/lib/widgets/week_view_widget.dart @@ -14,6 +14,12 @@ class WeekViewWidget extends StatelessWidget { return WeekView( key: state, width: width, + showLiveTimeLineInAllDays: true, + timeLineWidth: 65, + liveTimeIndicatorSettings: LiveTimeIndicatorSettings( + color: Colors.redAccent, + showTime: true, + ), onEventTap: (events, date) { Navigator.of(context).push( MaterialPageRoute( diff --git a/lib/src/components/_internal_components.dart b/lib/src/components/_internal_components.dart index f1658124..1de3e334 100644 --- a/lib/src/components/_internal_components.dart +++ b/lib/src/components/_internal_components.dart @@ -29,8 +29,8 @@ class LiveTimeIndicator extends StatefulWidget { final double timeLineWidth; /// settings for time line. Defines color, extra offset, - /// and height of indicator. - final HourIndicatorSettings liveTimeIndicatorSettings; + /// height of indicator and also allow to show time with custom format. + final LiveTimeIndicatorSettings liveTimeIndicatorSettings; /// Defines height occupied by one minute. final double heightPerMinute; @@ -79,6 +79,12 @@ class _LiveTimeIndicatorState extends State { @override Widget build(BuildContext context) { + final currentHour = _currentTime.hourOfPeriod.appendLeadingZero(); + final currentMinute = _currentTime.minute.appendLeadingZero(); + final currentPeriod = _currentTime.period.name; + final timeString = widget.liveTimeIndicatorSettings.timeStringBuilder + ?.call(DateTime.now()) ?? + '$currentHour:$currentMinute $currentPeriod'; return CustomPaint( size: Size(widget.width, widget.height), painter: CurrentTimeLinePainter( @@ -88,13 +94,21 @@ class _LiveTimeIndicatorState extends State { widget.timeLineWidth + widget.liveTimeIndicatorSettings.offset, _currentTime.getTotalMinutes * widget.heightPerMinute, ), + timeString: timeString, + showBullet: widget.liveTimeIndicatorSettings.showBullet, + showTime: widget.liveTimeIndicatorSettings.showTime, + showTimeBackgroundView: + widget.liveTimeIndicatorSettings.showTimeBackgroundView, + bulletRadius: widget.liveTimeIndicatorSettings.bulletRadius, + timeBackgroundViewWidth: + widget.liveTimeIndicatorSettings.timeBackgroundViewWidth, ), ); } } /// Time line to display time at left side of day or week view. -class TimeLine extends StatelessWidget { +class TimeLine extends StatefulWidget { /// Width of timeline final double timeLineWidth; @@ -116,6 +130,10 @@ class TimeLine extends StatelessWidget { /// Flag to display quarter hours. final bool showQuarterHours; + /// settings for time line. Defines color, extra offset, + /// height of indicator and also allow to show time with custom format. + final LiveTimeIndicatorSettings liveTimeIndicatorSettings; + static DateTime get _date => DateTime.now(); double get _halfHourHeight => hourHeight / 2; @@ -130,53 +148,95 @@ class TimeLine extends StatelessWidget { required this.timeLineBuilder, this.showHalfHours = false, this.showQuarterHours = false, + required this.liveTimeIndicatorSettings, }) : super(key: key); + @override + State createState() => _TimeLineState(); +} + +class _TimeLineState extends State { + late Timer _timer; + late TimeOfDay _currentTime = TimeOfDay.now(); + + @override + void initState() { + super.initState(); + _timer = Timer.periodic(Duration(seconds: 1), _onTick); + } + + @override + void dispose() { + _timer.cancel(); + super.dispose(); + } + + /// Creates an recursive call that runs every 1 seconds. + /// This will rebuild TimeLine every second. This will allow us + /// to show/hide time line when there is overlap with + /// live time line indicator in Week and Day view. + void _onTick(Timer? timer) { + final time = TimeOfDay.now(); + if (time != _currentTime && mounted) { + _currentTime = time; + setState(() {}); + } + } + @override Widget build(BuildContext context) { return ConstrainedBox( - key: ValueKey(hourHeight), + key: ValueKey(widget.hourHeight), constraints: BoxConstraints( - maxWidth: timeLineWidth, - minWidth: timeLineWidth, - maxHeight: height, - minHeight: height, + maxWidth: widget.timeLineWidth, + minWidth: widget.timeLineWidth, + maxHeight: widget.height, + minHeight: widget.height, ), child: Stack( children: [ for (int i = 1; i < Constants.hoursADay; i++) _timelinePositioned( - topPosition: hourHeight * i - timeLineOffset, - bottomPosition: height - (hourHeight * (i + 1)) + timeLineOffset, + topPosition: widget.hourHeight * i - widget.timeLineOffset, + bottomPosition: widget.height - + (widget.hourHeight * (i + 1)) + + widget.timeLineOffset, hour: i, ), - if (showHalfHours) + if (widget.showHalfHours) for (int i = 0; i < Constants.hoursADay; i++) _timelinePositioned( - topPosition: hourHeight * i - timeLineOffset + _halfHourHeight, - bottomPosition: - height - (hourHeight * (i + 1)) + timeLineOffset, + topPosition: widget.hourHeight * i - + widget.timeLineOffset + + widget._halfHourHeight, + bottomPosition: widget.height - + (widget.hourHeight * (i + 1)) + + widget.timeLineOffset, hour: i, minutes: 30, ), - if (showQuarterHours) + if (widget.showQuarterHours) for (int i = 0; i < Constants.hoursADay; i++) ...[ /// this is for 15 minutes _timelinePositioned( - topPosition: - hourHeight * i - timeLineOffset + hourHeight * 0.25, - bottomPosition: - height - (hourHeight * (i + 1)) + timeLineOffset, + topPosition: widget.hourHeight * i - + widget.timeLineOffset + + widget.hourHeight * 0.25, + bottomPosition: widget.height - + (widget.hourHeight * (i + 1)) + + widget.timeLineOffset, hour: i, minutes: 15, ), /// this is for 45 minutes _timelinePositioned( - topPosition: - hourHeight * i - timeLineOffset + hourHeight * 0.75, - bottomPosition: - height - (hourHeight * (i + 1)) + timeLineOffset, + topPosition: widget.hourHeight * i - + widget.timeLineOffset + + widget.hourHeight * 0.75, + bottomPosition: widget.height - + (widget.hourHeight * (i + 1)) + + widget.timeLineOffset, hour: i, minutes: 45, ), @@ -186,27 +246,34 @@ class TimeLine extends StatelessWidget { ); } + /// To avoid overlap of live time line indicator, show time line when + /// current min is less than 45 min and is previous hour or + /// current min is greater than 15 min and is current hour Widget _timelinePositioned({ required double topPosition, required double bottomPosition, required int hour, int minutes = 0, }) { - return Positioned( - top: topPosition, - left: 0, - right: 0, - bottom: bottomPosition, - child: Container( - height: hourHeight, - width: timeLineWidth, - child: timeLineBuilder.call( - DateTime( - _date.year, - _date.month, - _date.day, - hour, - minutes, + return Visibility( + visible: !((_currentTime.minute >= 45 && _currentTime.hour == hour - 1) || + (_currentTime.minute <= 15 && _currentTime.hour == hour)), + child: Positioned( + top: topPosition, + left: 0, + right: 0, + bottom: bottomPosition, + child: Container( + height: widget.hourHeight, + width: widget.timeLineWidth, + child: widget.timeLineBuilder.call( + DateTime( + TimeLine._date.year, + TimeLine._date.month, + TimeLine._date.day, + hour, + minutes, + ), ), ), ), diff --git a/lib/src/day_view/_internal_day_view_page.dart b/lib/src/day_view/_internal_day_view_page.dart index 47c4fbc4..fd2b91bb 100644 --- a/lib/src/day_view/_internal_day_view_page.dart +++ b/lib/src/day_view/_internal_day_view_page.dart @@ -47,7 +47,7 @@ class InternalDayViewPage extends StatelessWidget { final bool showLiveLine; /// Settings for live time indicator. - final HourIndicatorSettings liveTimeIndicatorSettings; + final LiveTimeIndicatorSettings liveTimeIndicatorSettings; /// Height occupied by one minute of time span. final double heightPerMinute; @@ -247,6 +247,7 @@ class InternalDayViewPage extends StatelessWidget { showHalfHours: showHalfHours, showQuarterHours: showQuarterHours, key: ValueKey(heightPerMinute), + liveTimeIndicatorSettings: liveTimeIndicatorSettings, ), if (showLiveLine && liveTimeIndicatorSettings.height > 0) IgnorePointer( diff --git a/lib/src/day_view/day_view.dart b/lib/src/day_view/day_view.dart index 2430f809..d8c457f5 100644 --- a/lib/src/day_view/day_view.dart +++ b/lib/src/day_view/day_view.dart @@ -87,8 +87,8 @@ class DayView extends StatefulWidget { /// Defines settings for live time indicator. /// - /// Pass [HourIndicatorSettings.none] to remove live time indicator. - final HourIndicatorSettings? liveTimeIndicatorSettings; + /// Pass [LiveTimeIndicatorSettings.none] to remove live time indicator. + final LiveTimeIndicatorSettings? liveTimeIndicatorSettings; /// Defines settings for half hour indication lines. /// @@ -297,7 +297,7 @@ class DayViewState extends State> { late HourIndicatorSettings _quarterHourIndicatorSettings; late CustomHourLinePainter _hourLinePainter; - late HourIndicatorSettings _liveTimeIndicatorSettings; + late LiveTimeIndicatorSettings _liveTimeIndicatorSettings; late PageController _pageController; @@ -505,7 +505,7 @@ class DayViewState extends State> { _timeLineWidth = widget.timeLineWidth ?? _width * 0.13; _liveTimeIndicatorSettings = widget.liveTimeIndicatorSettings ?? - HourIndicatorSettings( + LiveTimeIndicatorSettings( color: Constants.defaultLiveTimeIndicatorColor, height: widget.heightPerMinute, offset: 5 + widget.verticalLineOffset, diff --git a/lib/src/extensions.dart b/lib/src/extensions.dart index b26c67da..e13d0966 100644 --- a/lib/src/extensions.dart +++ b/lib/src/extensions.dart @@ -192,3 +192,9 @@ extension MyList on List { extension TimerOfDayExtension on TimeOfDay { int get getTotalMinutes => hour * 60 + minute; } + +extension IntExtension on int { + String appendLeadingZero() { + return toString().padLeft(2, '0'); + } +} diff --git a/lib/src/modals.dart b/lib/src/modals.dart index b4371b10..73039972 100644 --- a/lib/src/modals.dart +++ b/lib/src/modals.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'enumerations.dart'; +import 'typedefs.dart'; /// Settings for hour lines class HourIndicatorSettings { @@ -30,3 +31,53 @@ class HourIndicatorSettings { height: 0.0, ); } + +/// Settings for live time line +class LiveTimeIndicatorSettings { + /// Color of time indicator. + final Color color; + + /// Height of time indicator. + final double height; + + /// offset of time indicator. + final double offset; + + /// StringProvider for time string + final StringProvider? timeStringBuilder; + + /// Flag to show bullet at left side or not. + final bool showBullet; + + /// Flag to show time on live time line. + final bool showTime; + + /// Flag to show time backgroud view. + final bool showTimeBackgroundView; + + /// Radius of bullet. + final double bulletRadius; + + /// Width of time backgroud view. + final double timeBackgroundViewWidth; + + /// Settings for live time line + const LiveTimeIndicatorSettings({ + this.height = 1.0, + this.offset = 5.0, + this.color = Colors.grey, + this.timeStringBuilder, + this.showBullet = true, + this.showTime = false, + this.showTimeBackgroundView = false, + this.bulletRadius = 5.0, + this.timeBackgroundViewWidth = 60.0, + }) : assert(height >= 0, "Height must be greater than or equal to 0."); + + factory LiveTimeIndicatorSettings.none() => LiveTimeIndicatorSettings( + color: Colors.transparent, + height: 0.0, + offset: 0.0, + showBullet: false, + ); +} diff --git a/lib/src/painters.dart b/lib/src/painters.dart index e607a81c..9659d9c7 100644 --- a/lib/src/painters.dart +++ b/lib/src/painters.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a MIT-style license // that can be found in the LICENSE file. +import 'dart:math'; + import 'package:flutter/material.dart'; import 'constants.dart'; @@ -251,19 +253,35 @@ class CurrentTimeLinePainter extends CustomPainter { /// Radius of bullet. final double bulletRadius; + /// Time string + final String timeString; + + /// Flag to show time at left side or not. + final bool showTime; + + /// Flag to show time backgroud view. + final bool showTimeBackgroundView; + + /// Width of time backgroud view. + final double timeBackgroundViewWidth; + /// Paints a single horizontal line at [offset]. CurrentTimeLinePainter({ - this.showBullet = true, + required this.showBullet, required this.color, required this.height, required this.offset, - this.bulletRadius = 5, + required this.bulletRadius, + required this.timeString, + required this.showTime, + required this.showTimeBackgroundView, + required this.timeBackgroundViewWidth, }); @override void paint(Canvas canvas, Size size) { canvas.drawLine( - Offset(offset.dx, offset.dy), + Offset(offset.dx - (showBullet ? 0 : 8), offset.dy), Offset(size.width, offset.dy), Paint() ..color = color @@ -273,6 +291,37 @@ class CurrentTimeLinePainter extends CustomPainter { if (showBullet) canvas.drawCircle( Offset(offset.dx, offset.dy), bulletRadius, Paint()..color = color); + + if (showTimeBackgroundView) + canvas.drawRRect( + RRect.fromRectAndRadius( + Rect.fromLTWH( + max(3, offset.dx - 68), + offset.dy - 11, + timeBackgroundViewWidth, + 24, + ), + Radius.circular(12), + ), + Paint() + ..color = color + ..style = PaintingStyle.fill + ..strokeWidth = bulletRadius, + ); + + if (showTime) + TextPainter( + textDirection: TextDirection.ltr, + text: TextSpan( + text: timeString, + style: TextStyle( + fontSize: 12.0, + color: showTimeBackgroundView ? Colors.white : color, + ), + ), + ) + ..layout() + ..paint(canvas, Offset(offset.dx - 62, offset.dy - 6)); } @override @@ -280,5 +329,11 @@ class CurrentTimeLinePainter extends CustomPainter { oldDelegate is CurrentTimeLinePainter && (color != oldDelegate.color || height != oldDelegate.height || - offset != oldDelegate.offset); + offset != oldDelegate.offset || + bulletRadius != oldDelegate.bulletRadius || + timeString != oldDelegate.timeString || + timeBackgroundViewWidth != oldDelegate.timeBackgroundViewWidth || + showBullet != oldDelegate.showBullet || + showTime != oldDelegate.showTime || + showTimeBackgroundView != oldDelegate.showTimeBackgroundView); } diff --git a/lib/src/week_view/_internal_week_view_page.dart b/lib/src/week_view/_internal_week_view_page.dart index 97287571..f85a972a 100644 --- a/lib/src/week_view/_internal_week_view_page.dart +++ b/lib/src/week_view/_internal_week_view_page.dart @@ -50,7 +50,7 @@ class InternalWeekViewPage extends StatelessWidget { final bool showLiveLine; /// Settings for live time indicator. - final HourIndicatorSettings liveTimeIndicatorSettings; + final LiveTimeIndicatorSettings liveTimeIndicatorSettings; /// Height occupied by one minute time span. final double heightPerMinute; @@ -289,14 +289,6 @@ class InternalWeekViewPage extends StatelessWidget { quarterHourIndicatorSettings.dashSpaceWidth, ), ), - if (showLiveLine && liveTimeIndicatorSettings.height > 0) - LiveTimeIndicator( - liveTimeIndicatorSettings: liveTimeIndicatorSettings, - width: width, - height: height, - heightPerMinute: heightPerMinute, - timeLineWidth: timeLineWidth, - ), Align( alignment: Alignment.centerRight, child: SizedBox( @@ -358,7 +350,16 @@ class InternalWeekViewPage extends StatelessWidget { timeLineBuilder: timeLineBuilder, showHalfHours: showHalfHours, showQuarterHours: showQuarterHours, + liveTimeIndicatorSettings: liveTimeIndicatorSettings, ), + if (showLiveLine && liveTimeIndicatorSettings.height > 0) + LiveTimeIndicator( + liveTimeIndicatorSettings: liveTimeIndicatorSettings, + width: width, + height: height, + heightPerMinute: heightPerMinute, + timeLineWidth: timeLineWidth, + ), ], ), ), diff --git a/lib/src/week_view/week_view.dart b/lib/src/week_view/week_view.dart index f8054eb9..664a68b2 100644 --- a/lib/src/week_view/week_view.dart +++ b/lib/src/week_view/week_view.dart @@ -98,7 +98,7 @@ class WeekView extends StatefulWidget { final HourIndicatorSettings? quarterHourIndicatorSettings; /// Settings for live time indicator settings. - final HourIndicatorSettings? liveTimeIndicatorSettings; + final LiveTimeIndicatorSettings? liveTimeIndicatorSettings; /// duration for page transition while changing the week. final Duration pageTransitionDuration; @@ -303,7 +303,7 @@ class WeekViewState extends State> { late CustomHourLinePainter _hourLinePainter; late HourIndicatorSettings _halfHourIndicatorSettings; - late HourIndicatorSettings _liveTimeIndicatorSettings; + late LiveTimeIndicatorSettings _liveTimeIndicatorSettings; late HourIndicatorSettings _quarterHourIndicatorSettings; late PageController _pageController; @@ -546,10 +546,9 @@ class WeekViewState extends State> { _timeLineWidth = widget.timeLineWidth ?? _width * 0.13; _liveTimeIndicatorSettings = widget.liveTimeIndicatorSettings ?? - HourIndicatorSettings( + LiveTimeIndicatorSettings( color: Constants.defaultLiveTimeIndicatorColor, height: widget.heightPerMinute, - offset: 5, ); assert(_liveTimeIndicatorSettings.height < _hourHeight, diff --git a/pubspec.yaml b/pubspec.yaml index 3a680b48..29616f12 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,7 @@ homepage: https://github.com/SimformSolutionsPvtLtd/flutter_calendar_view issue_tracker: https://github.com/SimformSolutionsPvtLtd/flutter_calendar_view/issues?q=is%3Aissue+is%3Aopen+label%3Abug environment: - sdk: ">=2.12.0 <4.0.0" + sdk: ">=2.15.0 <4.0.0" flutter: ">=1.17.0" dependencies: