Skip to content

Commit

Permalink
feat: Fixes issue #412: ✨ Add support for RTL directionality
Browse files Browse the repository at this point in the history
  • Loading branch information
shubham-jitiya-simform committed Jan 16, 2025
1 parent 469cc29 commit 5deb92c
Show file tree
Hide file tree
Showing 14 changed files with 274 additions and 86 deletions.
1 change: 1 addition & 0 deletions example/lib/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class AppConstants {
AppConstants._();

static final List<String> weekTitles = ['M', 'T', 'W', 'T', 'F', 'S', 'S'];
static final ltr = '\u202A'; // Use this to keep text direction LTR

static OutlineInputBorder inputBorder = OutlineInputBorder(
borderRadius: BorderRadius.circular(7),
Expand Down
23 changes: 13 additions & 10 deletions example/lib/pages/day_view_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,20 @@ class DayViewPageDemo extends StatefulWidget {
class _DayViewPageDemoState extends State<DayViewPageDemo> {
@override
Widget build(BuildContext context) {
return ResponsiveWidget(
webWidget: WebHomePage(
selectedView: CalendarView.day,
),
mobileWidget: Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
elevation: 8,
onPressed: () => context.pushRoute(CreateEventPage()),
return Directionality(
textDirection: TextDirection.rtl,
child: ResponsiveWidget(
webWidget: WebHomePage(
selectedView: CalendarView.day,
),
mobileWidget: Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
elevation: 8,
onPressed: () => context.pushRoute(CreateEventPage()),
),
body: DayViewWidget(),
),
body: DayViewWidget(),
),
);
}
Expand Down
23 changes: 13 additions & 10 deletions example/lib/pages/month_view_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,20 @@ class MonthViewPageDemo extends StatefulWidget {
class _MonthViewPageDemoState extends State<MonthViewPageDemo> {
@override
Widget build(BuildContext context) {
return ResponsiveWidget(
webWidget: WebHomePage(
selectedView: CalendarView.month,
),
mobileWidget: Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
elevation: 8,
onPressed: () => context.pushRoute(CreateEventPage()),
return Directionality(
textDirection: TextDirection.rtl,
child: ResponsiveWidget(
webWidget: WebHomePage(
selectedView: CalendarView.month,
),
mobileWidget: Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
elevation: 8,
onPressed: () => context.pushRoute(CreateEventPage()),
),
body: MonthViewWidget(),
),
body: MonthViewWidget(),
),
);
}
Expand Down
23 changes: 13 additions & 10 deletions example/lib/pages/week_view_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,20 @@ class WeekViewDemo extends StatefulWidget {
class _WeekViewDemoState extends State<WeekViewDemo> {
@override
Widget build(BuildContext context) {
return ResponsiveWidget(
webWidget: WebHomePage(
selectedView: CalendarView.week,
),
mobileWidget: Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
elevation: 8,
onPressed: () => context.pushRoute(CreateEventPage()),
return Directionality(
textDirection: TextDirection.rtl,
child: ResponsiveWidget(
webWidget: WebHomePage(
selectedView: CalendarView.week,
),
mobileWidget: Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
elevation: 8,
onPressed: () => context.pushRoute(CreateEventPage()),
),
body: WeekViewWidget(),
),
body: WeekViewWidget(),
),
);
}
Expand Down
14 changes: 10 additions & 4 deletions example/lib/widgets/day_view_widget.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:calendar_view/calendar_view.dart';
import 'package:example/constants.dart';
import 'package:flutter/material.dart';

import '../pages/event_details_page.dart';
Expand All @@ -23,7 +24,10 @@ class DayViewWidget extends StatelessWidget {
heightPerMinute: 3,
timeLineBuilder: _timeLineBuilder,
scrollPhysics: const BouncingScrollPhysics(),
eventArranger: SideEventArranger(maxWidth: 30),
eventArranger: SideEventArranger(
maxWidth: 30,
directionality: Directionality.of(context),
),
hourIndicatorSettings: HourIndicatorSettings(
color: Theme.of(context).dividerColor,
),
Expand Down Expand Up @@ -71,9 +75,10 @@ class DayViewWidget extends StatelessWidget {
Positioned.fill(
top: -8,
right: 8,
left: 8,
child: Text(
"${date.hour}:${date.minute}",
textAlign: TextAlign.right,
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.black.withAlpha(50),
fontStyle: FontStyle.italic,
Expand All @@ -92,9 +97,10 @@ class DayViewWidget extends StatelessWidget {
Positioned.fill(
top: -8,
right: 8,
left: 8,
child: Text(
"$hour ${date.hour ~/ 12 == 0 ? "am" : "pm"}",
textAlign: TextAlign.right,
"${AppConstants.ltr} $hour ${date.hour ~/ 12 == 0 ? "am" : "pm"}",
textAlign: TextAlign.center,
),
),
],
Expand Down
34 changes: 33 additions & 1 deletion example/lib/widgets/week_view_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,26 @@ 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),
eventArranger: SideEventArranger(
maxWidth: 30,
directionality: Directionality.of(context),
),
timeLineWidth: 65,
scrollPhysics: const BouncingScrollPhysics(),
liveTimeIndicatorSettings: LiveTimeIndicatorSettings(
color: Colors.redAccent,
showTime: true,
showTimeBackgroundView: true,
),
onTimestampTap: (date) {
SnackBar snackBar = SnackBar(
Expand All @@ -45,4 +57,24 @@ class WeekViewWidget extends StatelessWidget {
},
);
}

// TODO(Shubham): Include in readme to guide how to support RTL for string like below
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";
}
}
}
5 changes: 4 additions & 1 deletion lib/src/components/_internal_components.dart
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,11 @@ class _LiveTimeIndicatorState extends State<LiveTimeIndicator> {
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,
),
Expand Down Expand Up @@ -230,6 +232,7 @@ class _TimeLineState extends State<TimeLine> {
),
child: Stack(
children: [
// TODO(Shubham): Update padding for RTL
for (int i = widget.startHour + 1; i < widget.endHour; i++)
_timelinePositioned(
topPosition: widget.hourHeight * (i - widget.startHour) -
Expand Down
31 changes: 21 additions & 10 deletions lib/src/components/day_view_components.dart
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,15 @@ 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,
this.textDirection,
}) : super(key: key);

@override
Expand All @@ -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,
Expand Down Expand Up @@ -212,14 +218,19 @@ class FullDayEventView<T> extends StatelessWidget {
margin: const EdgeInsets.all(5.0),
padding: const EdgeInsets.all(1.0),
height: 24,
child: Text(
events[index].title,
style: titleStyle ??
TextStyle(
fontSize: 16,
color: events[index].color.accent,
),
maxLines: 1,
child: Align(
alignment: Directionality.of(context) == TextDirection.ltr
? Alignment.centerLeft
: Alignment.centerRight,
child: Text(
events[index].title,
style: titleStyle ??
TextStyle(
fontSize: 16,
color: events[index].color.accent,
),
maxLines: 1,
),
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
Expand Down
16 changes: 15 additions & 1 deletion lib/src/day_view/_internal_day_view_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,15 @@ class _InternalDayViewPageState<T extends Object?>
@override
Widget build(BuildContext context) {
final fullDayEventList = widget.controller.getFullDayEvent(widget.date);
final hourLineOffset = (Directionality.of(context) == TextDirection.ltr
? widget.timeLineWidth
: 0) +
widget.hourIndicatorSettings.offset;
final halfHourOffset = (Directionality.of(context) == TextDirection.ltr
? widget.timeLineWidth
: 0) +
widget.halfHourIndicatorSettings.offset;

return Container(
height: widget.height,
width: widget.width,
Expand Down Expand Up @@ -269,6 +278,8 @@ class _InternalDayViewPageState<T extends Object?>
widget.halfHourIndicatorSettings.dashSpaceWidth,
startHour: widget.startHour,
endHour: widget.endHour,
textDirection: Directionality.of(context),
timelineWidth: widget.timeLineWidth,
),
),
if (widget.showQuarterHours)
Expand Down Expand Up @@ -296,8 +307,11 @@ class _InternalDayViewPageState<T extends Object?>
date: widget.date,
minuteSlotSize: widget.minuteSlotSize,
),
// TODO(Shubham): Update for directionality
Align(
alignment: Alignment.centerRight,
alignment: Directionality.of(context) == TextDirection.ltr
? Alignment.centerRight
: Alignment.centerLeft,
child: EventGenerator<T>(
height: widget.height,
date: widget.date,
Expand Down
5 changes: 4 additions & 1 deletion lib/src/day_view/day_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -731,10 +731,13 @@ class DayViewState<T extends Object?> extends State<DayView<T>> {
int startHour,
int endHour,
) {
final directionality = Directionality.of(context);
return HourLinePainter(
lineColor: lineColor,
lineHeight: lineHeight,
offset: offset,
timelineWidth: widget.timeLineWidth,
textDirection: directionality,
offset: directionality == TextDirection.ltr ? offset : 0,
minuteHeight: minuteHeight,
verticalLineOffset: verticalLineOffset,
showVerticalLine: showVerticalLine,
Expand Down
10 changes: 8 additions & 2 deletions lib/src/event_arrangers/side_event_arranger.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class SideEventArranger<T extends Object?> extends EventArranger<T> {
const SideEventArranger({
this.maxWidth,
this.includeEdges = false,
this.directionality = TextDirection.ltr,
});

/// Decides whether events that are overlapping on edge
Expand All @@ -26,6 +27,9 @@ class SideEventArranger<T extends Object?> extends EventArranger<T> {
/// If max width is not specified, slots will expand to fill the cell.
final double? maxWidth;

/// Defines the directionality LRT/RTL
final TextDirection directionality;

/// {@macro event_arranger_arrange_method_doc}
///
/// Make sure that all the events that are passed in [events], must be in
Expand Down Expand Up @@ -130,9 +134,11 @@ class SideEventArranger<T extends Object?> extends EventArranger<T> {
final top = (startTime.getTotalMinutes - (startHour * 60)) *
heightPerMinute;

final isLtr = directionality == TextDirection.ltr;

return OrganizedCalendarEventData<T>(
left: offset,
right: totalWidth - (offset + slotWidth),
left: isLtr ? offset : totalWidth - (offset + slotWidth),
right: isLtr ? totalWidth - (offset + slotWidth) : offset,
top: top,
bottom: bottom,
startDuration: startTime,
Expand Down
Loading

0 comments on commit 5deb92c

Please sign in to comment.