From 619cf99c99e784f57bf9062d024907279e98b7f4 Mon Sep 17 00:00:00 2001 From: Shubham Jitiya Date: Thu, 9 Jan 2025 18:41:08 +0530 Subject: [PATCH] =?UTF-8?q?feat:=20Fixes=20issue=20#412:=20=E2=9C=A8=20Add?= =?UTF-8?q?=20support=20for=20RTL=20directionality?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/lib/pages/week_view_page.dart | 15 +-- example/lib/widgets/week_view_widget.dart | 27 +++++ lib/src/components/_internal_components.dart | 4 +- lib/src/components/day_view_components.dart | 22 ++-- lib/src/painters.dart | 36 ++++++- .../week_view/_internal_week_view_page.dart | 100 +++++++++++++----- lib/src/week_view/week_view.dart | 5 + 7 files changed, 163 insertions(+), 46 deletions(-) diff --git a/example/lib/pages/week_view_page.dart b/example/lib/pages/week_view_page.dart index 3a183f5e..ac46e9ef 100644 --- a/example/lib/pages/week_view_page.dart +++ b/example/lib/pages/week_view_page.dart @@ -21,13 +21,16 @@ class _WeekViewDemoState extends State { webWidget: WebHomePage( selectedView: CalendarView.week, ), - mobileWidget: Scaffold( - floatingActionButton: FloatingActionButton( - child: Icon(Icons.add), - elevation: 8, - onPressed: () => context.pushRoute(CreateEventPage()), + mobileWidget: Directionality( + textDirection: TextDirection.rtl, + child: Scaffold( + floatingActionButton: FloatingActionButton( + child: Icon(Icons.add), + elevation: 8, + onPressed: () => context.pushRoute(CreateEventPage()), + ), + body: WeekViewWidget(), ), - body: WeekViewWidget(), ), ); } diff --git a/example/lib/widgets/week_view_widget.dart b/example/lib/widgets/week_view_widget.dart index 4222c531..3bb6c465 100644 --- a/example/lib/widgets/week_view_widget.dart +++ b/example/lib/widgets/week_view_widget.dart @@ -14,6 +14,13 @@ class WeekViewWidget extends StatelessWidget { return WeekView( key: state, width: width, + // textDirection: TextDirection.rtl, + headerStringBuilder: (DateTime date, {DateTime? secondaryDate}) => + _weekStringBuilder( + date, + secondaryDate: secondaryDate, + textDirection: Directionality.of(context), + ), showWeekends: true, showLiveTimeLineInAllDays: true, eventArranger: SideEventArranger(maxWidth: 30), @@ -22,6 +29,7 @@ class WeekViewWidget extends StatelessWidget { liveTimeIndicatorSettings: LiveTimeIndicatorSettings( color: Colors.redAccent, showTime: true, + showTimeBackgroundView: true, ), onTimestampTap: (date) { SnackBar snackBar = SnackBar( @@ -45,4 +53,23 @@ class WeekViewWidget extends StatelessWidget { }, ); } + + String _weekStringBuilder(DateTime date, + {DateTime? secondaryDate, TextDirection? textDirection}) { + final dateString = "${date.day} / ${date.month} / ${date.year}"; + final secondaryDateString = secondaryDate != null + ? "${secondaryDate.day} / ${secondaryDate.month} / ${secondaryDate.year}" + : ""; + // Unicode character for Left-to-Right Embedding + // (to enforce LTR in the string) + const ltr = '\u202A'; + // Unicode character for Pop Directional Formatting + // (to close the directional formatting) + const pop = '\u202C'; + if (textDirection == TextDirection.rtl) { + return "$ltr${secondaryDateString} to ${dateString}$pop"; + } else { + return "$ltr${dateString} to ${secondaryDateString}$pop"; + } + } } diff --git a/lib/src/components/_internal_components.dart b/lib/src/components/_internal_components.dart index a13bd432..51449115 100644 --- a/lib/src/components/_internal_components.dart +++ b/lib/src/components/_internal_components.dart @@ -111,9 +111,11 @@ class _LiveTimeIndicatorState extends State { size: Size(widget.width, widget.liveTimeIndicatorSettings.height), painter: CurrentTimeLinePainter( color: widget.liveTimeIndicatorSettings.color, + textDirection: Directionality.of(context), height: widget.liveTimeIndicatorSettings.height, + width: widget.width - widget.timeLineWidth - 12, offset: Offset( - widget.timeLineWidth + widget.liveTimeIndicatorSettings.offset, + widget.liveTimeIndicatorSettings.offset, (_currentTime.getTotalMinutes - startMinutes) * widget.heightPerMinute, ), diff --git a/lib/src/components/day_view_components.dart b/lib/src/components/day_view_components.dart index b8e28e9e..e5f3d108 100644 --- a/lib/src/components/day_view_components.dart +++ b/lib/src/components/day_view_components.dart @@ -120,13 +120,16 @@ class DefaultTimeLineMark extends StatelessWidget { /// Text style for time string. final TextStyle? markingStyle; + final TextDirection? textDirection; + /// Time marker for timeline used in week and day view. - const DefaultTimeLineMark({ - Key? key, - required this.date, - this.markingStyle, - this.timeStringBuilder, - }) : super(key: key); + const DefaultTimeLineMark( + {Key? key, + required this.date, + this.markingStyle, + this.timeStringBuilder, + this.textDirection}) + : super(key: key); @override Widget build(BuildContext context) { @@ -139,10 +142,13 @@ class DefaultTimeLineMark extends StatelessWidget { return Transform.translate( offset: Offset(0, -7.5), child: Padding( - padding: const EdgeInsets.only(right: 7.0), + padding: const EdgeInsets.only(right: 7.0, left: 7.0), child: Text( timeString, - textAlign: TextAlign.right, + // textDirection: Directionality.of(context), + textAlign: Directionality.of(context) == TextDirection.ltr + ? TextAlign.right + : TextAlign.left, style: markingStyle ?? TextStyle( fontSize: 15.0, diff --git a/lib/src/painters.dart b/lib/src/painters.dart index 4498bfb3..54fe19bd 100644 --- a/lib/src/painters.dart +++ b/lib/src/painters.dart @@ -84,6 +84,8 @@ class HourLinePainter extends CustomPainter { } } + // TODO(Shubham): Draws vertical line at start disable it to remove leading vertical line + if (showVerticalLine) { if (lineStyle == LineStyle.dashed) { var startY = 0.0; @@ -261,10 +263,12 @@ class CurrentTimeLinePainter extends CustomPainter { /// Height of time indicator. final double height; + final double width; /// offset of time indicator. final Offset offset; + // TODO(Shubham): Update docs /// Flag to show bullet at left side or not. final bool showBullet; @@ -282,6 +286,7 @@ class CurrentTimeLinePainter extends CustomPainter { /// Width of time backgroud view. final double timeBackgroundViewWidth; + final TextDirection textDirection; /// Paints a single horizontal line at [offset]. CurrentTimeLinePainter({ @@ -294,28 +299,43 @@ class CurrentTimeLinePainter extends CustomPainter { required this.showTime, required this.showTimeBackgroundView, required this.timeBackgroundViewWidth, + required this.width, + this.textDirection = TextDirection.ltr, }); @override void paint(Canvas canvas, Size size) { + // TODO(Shubham): Check size.width is line width or not canvas.drawLine( Offset(offset.dx - (showBullet ? 0 : 8), offset.dy), - Offset(size.width, offset.dy), + Offset( + textDirection == TextDirection.ltr ? size.width : width, offset.dy), Paint() ..color = color ..strokeWidth = height, ); if (showBullet) { - canvas.drawCircle( - Offset(offset.dx, offset.dy), bulletRadius, Paint()..color = color); + if (textDirection == TextDirection.ltr) { + canvas.drawCircle( + Offset(offset.dx + timeBackgroundViewWidth, offset.dy), + bulletRadius, + Paint()..color = color); + } else { + canvas.drawCircle(Offset(offset.dx + width, offset.dy), bulletRadius, + Paint()..color = color); + } } if (showTimeBackgroundView) { + final dx = textDirection == TextDirection.ltr + ? offset.dx - 4 + : offset.dx + width + 4; + canvas.drawRRect( RRect.fromRectAndRadius( Rect.fromLTWH( - max(3, offset.dx - 68), + max(3, dx), offset.dy - 11, timeBackgroundViewWidth, 24, @@ -341,7 +361,13 @@ class CurrentTimeLinePainter extends CustomPainter { ), ) ..layout() - ..paint(canvas, Offset(offset.dx - 62, offset.dy - 6)); + ..paint( + canvas, + Offset( + textDirection == TextDirection.ltr + ? offset.dx + : offset.dx + width + 7, + offset.dy - 6)); } } diff --git a/lib/src/week_view/_internal_week_view_page.dart b/lib/src/week_view/_internal_week_view_page.dart index 85d36b54..18ea0050 100644 --- a/lib/src/week_view/_internal_week_view_page.dart +++ b/lib/src/week_view/_internal_week_view_page.dart @@ -166,6 +166,8 @@ class InternalWeekViewPage extends StatefulWidget { /// This method will be called when user taps on timestamp in timeline. final TimestampCallback? onTimestampTap; + final TextDirection textDirection; + /// A single page for week view. const InternalWeekViewPage({ Key? key, @@ -216,6 +218,7 @@ class InternalWeekViewPage extends StatefulWidget { required this.weekViewScrollController, this.lastScrollOffset = 0.0, this.keepScrollOffset = false, + this.textDirection = TextDirection.ltr, }) : super(key: key); @override @@ -251,6 +254,7 @@ class _InternalWeekViewPageState @override Widget build(BuildContext context) { final filteredDates = _filteredDate(); + final textDirection = Directionality.of(context); return Container( height: widget.height + widget.weekTitleHeight, width: widget.width, @@ -311,6 +315,7 @@ class _InternalWeekViewPageState vertical: 2, horizontal: 1, ), + // TODO(Shubham): Add direction child: Text( widget.fullDayHeaderTitle, textAlign: @@ -353,25 +358,50 @@ class _InternalWeekViewPageState width: widget.width, child: Stack( children: [ - CustomPaint( - size: Size(widget.width, widget.height), - painter: HourLinePainter( - lineColor: widget.hourIndicatorSettings.color, - lineHeight: widget.hourIndicatorSettings.height, - offset: widget.timeLineWidth + - widget.hourIndicatorSettings.offset, - minuteHeight: widget.heightPerMinute, - verticalLineOffset: widget.verticalLineOffset, - showVerticalLine: widget.showVerticalLine, - lineStyle: widget.hourIndicatorSettings.lineStyle, - dashWidth: widget.hourIndicatorSettings.dashWidth, - dashSpaceWidth: - widget.hourIndicatorSettings.dashSpaceWidth, - emulateVerticalOffsetBy: widget.emulateVerticalOffsetBy, - startHour: widget.startHour, - endHour: widget.endHour, + // TODO(Shubham): Width of horizontal paint line + if (true) + Positioned( + left: Directionality.of(context) == TextDirection.ltr + ? widget.timeLineWidth + : 0, + // TODO(Shubham): Check padding of 6 from somewhere + right: Directionality.of(context) == TextDirection.ltr + ? 0 + : widget.timeLineWidth + 6, + child: CustomPaint( + size: Size( + widget.width, + widget.height, + ), + painter: HourLinePainter( + lineColor: widget.hourIndicatorSettings.color, + lineHeight: widget.hourIndicatorSettings.height, + offset: 0, + // TODO(Shubham): Update offset + // offset: + // Directionality.of(context) == TextDirection.ltr + // ? widget.timeLineWidth + + // widget.hourIndicatorSettings.offset + // : 0, + // offset: + // Directionality.of(context) == TextDirection.ltr + // ? widget.timeLineWidth + + // widget.hourIndicatorSettings.offset + // : widget.width - widget.timeLineWidth, + minuteHeight: widget.heightPerMinute, + verticalLineOffset: widget.verticalLineOffset, + showVerticalLine: widget.showVerticalLine, + lineStyle: widget.hourIndicatorSettings.lineStyle, + dashWidth: widget.hourIndicatorSettings.dashWidth, + dashSpaceWidth: + widget.hourIndicatorSettings.dashSpaceWidth, + emulateVerticalOffsetBy: + widget.emulateVerticalOffsetBy, + startHour: widget.startHour, + endHour: widget.endHour, + ), + ), ), - ), if (widget.showHalfHours) CustomPaint( size: Size(widget.width, widget.height), @@ -408,7 +438,9 @@ class _InternalWeekViewPageState ), ), Align( - alignment: Alignment.centerRight, + alignment: Directionality.of(context) == TextDirection.ltr + ? Alignment.centerRight + : Alignment.centerLeft, child: SizedBox( width: widget.weekTitleWidth * filteredDates.length, height: widget.height, @@ -420,12 +452,28 @@ class _InternalWeekViewPageState decoration: widget.showVerticalLine ? BoxDecoration( border: Border( - right: BorderSide( - color: widget - .hourIndicatorSettings.color, - width: widget - .hourIndicatorSettings.height, - ), + left: widget.textDirection == + TextDirection.ltr + ? BorderSide.none + : BorderSide( + color: widget + .hourIndicatorSettings + .color, + width: widget + .hourIndicatorSettings + .height, + ), + right: widget.textDirection == + TextDirection.ltr + ? BorderSide( + color: widget + .hourIndicatorSettings + .color, + width: widget + .hourIndicatorSettings + .height, + ) + : BorderSide.none, ), ) : null, @@ -486,7 +534,7 @@ class _InternalWeekViewPageState LiveTimeIndicator( liveTimeIndicatorSettings: widget.liveTimeIndicatorSettings, - width: widget.width, + width: widget.width - 8, height: widget.height, heightPerMinute: widget.heightPerMinute, timeLineWidth: widget.timeLineWidth, diff --git a/lib/src/week_view/week_view.dart b/lib/src/week_view/week_view.dart index b91efa13..2d06363a 100644 --- a/lib/src/week_view/week_view.dart +++ b/lib/src/week_view/week_view.dart @@ -250,6 +250,8 @@ class WeekView extends StatefulWidget { /// Flag to keep scrollOffset of pages on page change final bool keepScrollOffset; + final TextDirection textDirection; + /// Main widget for week view. const WeekView({ Key? key, @@ -309,6 +311,7 @@ class WeekView extends StatefulWidget { this.fullDayHeaderTitle = '', this.fullDayHeaderTextConfig, this.keepScrollOffset = false, + this.textDirection = TextDirection.ltr, this.onTimestampTap, }) : assert(!(onHeaderTitleTap != null && weekPageHeaderBuilder != null), "can't use [onHeaderTitleTap] & [weekPageHeaderBuilder] simultaneously"), @@ -533,6 +536,7 @@ class WeekViewState extends State> { _hourHeight.toString() + dates[0].toString()), height: _height, width: _width, + textDirection: widget.textDirection, weekTitleWidth: _weekTitleWidth, weekTitleHeight: widget.weekTitleHeight, weekDayBuilder: _weekDayBuilder, @@ -807,6 +811,7 @@ class WeekViewState extends State> { Widget _defaultTimeLineBuilder(DateTime date) => DefaultTimeLineMark( date: date, timeStringBuilder: widget.timeLineStringBuilder, + textDirection: widget.textDirection, ); /// Default timeline builder. This builder will be used if