Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

- Fixed `MonthViewBuilder` to be generic for improved type safety in `MonthView`. [#524](https://github.com/SimformSolutionsPvtLtd/flutter_calendar_view/issues/524)
- Added `DividerSettings` to customize the dividers in `WeekView` and `MultiDayView`. [#374](https://github.com/SimformSolutionsPvtLtd/flutter_calendar_view/issues/374), [#430](https://github.com/SimformSolutionsPvtLtd/flutter_calendar_view/issues/430), [#498](https://github.com/SimformSolutionsPvtLtd/flutter_calendar_view/issues/498)
- Added `timeSlotColorBuilder` in `DayView` and `WeekView` to customize background color. [#470](https://github.com/SimformSolutionsPvtLtd/flutter_calendar_view/issues/470)
- Added `timeSlotColorBuilder` in `DayView`, `WeekView` and `MultiDayView` to customize background color. [#470](https://github.com/SimformSolutionsPvtLtd/flutter_calendar_view/issues/470), [#535](https://github.com/SimformSolutionsPvtLtd/flutter_calendar_view/issues/535)
- Added `pageDate` parameter for timeline label customization with current timestamp fallback. [#527](https://github.com/SimformSolutionsPvtLtd/flutter_calendar_view/issues/527)
- Added `selectedDate` control to `MonthView` for external date management. [#233](https://github.com/SimformSolutionsPvtLtd/flutter_calendar_view/issues/233)
- [BREAKING] Added `isSelected` parameter to `CellBuilder` typedef in `MonthView`. Custom cell builders must be updated to accept this new parameter.
Expand Down
11 changes: 11 additions & 0 deletions example/lib/widgets/multi_day_view_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ class MultiDayViewWidget extends StatelessWidget {
thickness: 0.5,
height: 0.5,
),
timeSlotColorBuilder: (_, slotStartTime, __, ___) {
final hour = slotStartTime.hour;
final isBusinessHours = hour >= 9 && hour < 17;
final isLunchBreak = hour == 12;

return isLunchBreak
? Colors.orange.shade100
: isBusinessHours
? Colors.green.shade50
: Colors.transparent;
},
onTimestampTap: (date) {
SnackBar snackBar = SnackBar(
content: Text("On tap: ${date.hour} Hr : ${date.minute} Min"),
Expand Down
107 changes: 107 additions & 0 deletions lib/src/components/common_components.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import '../calendar_event_data.dart';
import '../constants.dart';
import '../enumerations.dart';
import '../extensions.dart';
import '../painters.dart';
import '../typedefs.dart';
import 'components.dart';

Expand Down Expand Up @@ -114,3 +115,109 @@ class DefaultEventTile<T> extends StatelessWidget {
}
}
}

/// Renders time-slot colored backgrounds for calendar views.
///
/// Accepts one or more [dates] (columns). For a single-day view pass a
/// one-element list; for week/multi-day views pass all visible dates.
/// Each column is painted with per-slot colors returned by
/// [timeSlotColorBuilder] and wrapped in a [RepaintBoundary] so repaints
/// are isolated to individual columns.
class TimeSlotBackgrounds extends StatelessWidget {
/// Dates to render (one column per date).
final List<DateTime> dates;

/// Pixel width of each date column.
final double columnWidth;

/// Total pixel height of the scrollable area.
final double height;

/// Pixel height per minute used to compute slot height.
final double heightPerMinute;

/// Duration of each time slot.
final MinuteSlotSize minuteSlotSize;

/// First hour shown in the view.
final int startHour;

/// Last hour shown in the view.
final int endHour;

/// Callback that returns a background [Color] for each slot.
final TimeSlotColorBuilder timeSlotColorBuilder;

const TimeSlotBackgrounds({
Key? key,
required this.dates,
required this.columnWidth,
required this.height,
required this.heightPerMinute,
required this.minuteSlotSize,
required this.startHour,
required this.endHour,
required this.timeSlotColorBuilder,
}) : super(key: key);

@override
Widget build(BuildContext context) {
// Number of minutes each slot occupies (e.g. 15, 30, or 60).
final slotMinutes = minuteSlotSize.minutes;
// Pixel height of a single time slot rectangle.
final heightPerSlot = heightPerMinute * slotMinutes;
// Total number of slots that fit between startHour and endHour.
final totalSlots = ((endHour - startHour) * 60) ~/ slotMinutes;
// Convenience Duration used when computing slot start/end DateTimes.
final slotDuration = Duration(minutes: slotMinutes);
// Whether the ambient text direction is left-to-right.
// Used to align the background layer correctly in RTL layouts.
final isLtr = Directionality.of(context) == TextDirection.ltr;

return Align(
alignment: isLtr ? Alignment.centerRight : Alignment.centerLeft,
child: SizedBox(
width: columnWidth * dates.length,
height: height,
child: Row(
children: List.generate(dates.length, (dayIndex) {
final dayDate = dates[dayIndex];
final dayStart = DateTime(
dayDate.year,
dayDate.month,
dayDate.day,
startHour,
);
final slotColors = List<Color>.generate(
totalSlots,
(slotIndex) {
final slotStartTime = dayStart.add(slotDuration * slotIndex);
final slotEndTime = slotStartTime.add(slotDuration);
return timeSlotColorBuilder(
dayDate,
slotStartTime,
slotEndTime,
slotIndex,
);
},
);
return ClipRect(
child: SizedBox(
width: columnWidth,
height: height,
child: RepaintBoundary(
child: CustomPaint(
painter: TimeSlotBackgroundPainter(
heightPerSlot: heightPerSlot,
slotColors: slotColors,
),
),
),
),
);
}),
),
),
);
}
}
58 changes: 10 additions & 48 deletions lib/src/day_view/_internal_day_view_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
import 'package:flutter/material.dart';

import '../components/_internal_components.dart';
import '../components/common_components.dart';
import '../components/event_scroll_notifier.dart';
import '../enumerations.dart';
import '../event_arrangers/event_arrangers.dart';
import '../event_controller.dart';
import '../extensions.dart';
import '../modals.dart';
import '../painters.dart';
import '../typedefs.dart';
Expand Down Expand Up @@ -246,59 +246,21 @@ class _InternalDayViewPageState<T extends Object?>
///
/// Returns a [Widget] rendering the colored background for all time slots.
Widget _buildTimeSlotBackground() {
// Extract the minute duration of each time slot (e.g., 15, 30, 60 minutes)
final slotMinutes = widget.minuteSlotSize.minutes;
// Calculate the pixel height occupied by one time slot
final heightPerSlot = widget.heightPerMinute * slotMinutes;
// Calculate the total number of time slots in the day view
// based on start and end hours
final totalSlots =
((widget.endHour - widget.startHour) * 60) ~/ slotMinutes;
final startDateTime = DateTime(
widget.date.year,
widget.date.month,
widget.date.day,
widget.startHour,
);
final slotDuration = Duration(minutes: slotMinutes);
// Generate a list of colors for each time slot of the day
final slotColors = List<Color>.generate(
totalSlots,
(slotIndex) {
final slotStartTime = startDateTime.add(slotDuration * slotIndex);
final slotEndTime = slotStartTime.add(slotDuration);
return widget.timeSlotColorBuilder!(
widget.date,
slotStartTime,
slotEndTime,
slotIndex,
);
},
);
final direction = Directionality.of(context);
// Calculate the width of the content area, excluding the time line and
// hour indicator lines
final contentWidth = widget.width -
widget.timeLineWidth -
widget.hourIndicatorSettings.offset -
widget.verticalLineOffset;

return Align(
alignment: direction == TextDirection.rtl
? Alignment.centerLeft
: Alignment.centerRight,
child: SizedBox(
width: contentWidth,
height: widget.height,
child: RepaintBoundary(
child: CustomPaint(
painter: TimeSlotBackgroundPainter(
heightPerSlot: heightPerSlot,
slotColors: slotColors,
),
),
),
),
return TimeSlotBackgrounds(
dates: [widget.date],
columnWidth: contentWidth,
height: widget.height,
heightPerMinute: widget.heightPerMinute,
minuteSlotSize: widget.minuteSlotSize,
startHour: widget.startHour,
endHour: widget.endHour,
timeSlotColorBuilder: widget.timeSlotColorBuilder!,
);
}

Expand Down
29 changes: 28 additions & 1 deletion lib/src/multi_day_view/_internal_multi_day_view_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import 'package:flutter/material.dart';

import '../components/_internal_components.dart';
import '../components/common_components.dart';
import '../components/event_scroll_notifier.dart';
import '../components/week_view_components.dart';
import '../enumerations.dart';
Expand Down Expand Up @@ -174,6 +175,9 @@ class InternalMultiDayViewPage<T extends Object?> extends StatefulWidget {
/// This method will be called when user taps on timestamp in timeline.
final TimestampCallback? onTimestampTap;

/// A callback for rendering custom time slot background colors.
final TimeSlotColorBuilder? timeSlotColorBuilder;

/// A single page for week view.
const InternalMultiDayViewPage(
{Key? key,
Expand Down Expand Up @@ -225,7 +229,8 @@ class InternalMultiDayViewPage<T extends Object?> extends StatefulWidget {
required this.multiDayViewScrollController,
this.lastScrollOffset = 0.0,
this.keepScrollOffset = false,
this.showMutliDayBottomLine = true})
this.showMutliDayBottomLine = true,
this.timeSlotColorBuilder})
: super(key: key);

@override
Expand Down Expand Up @@ -273,6 +278,25 @@ class _InternalMultiDayViewPageState<T extends Object?>
widget.scrollListener(scrollController);
}

/// Builds background layers for time slots in the multi-day view.
/// Uses [timeSlotColorBuilder] to determine each slot's color and paints
/// a grid of colored rectangles (one column per day, one row per slot).
///
/// Parameter: [filteredDates] — visible dates for the page.
/// Returns a [Widget] that paints the slot backgrounds.
Widget _buildMultiDayTimeSlotBackgrounds(List<DateTime> filteredDates) {
return TimeSlotBackgrounds(
dates: filteredDates,
columnWidth: widget.weekTitleWidth,
height: widget.height,
heightPerMinute: widget.heightPerMinute,
minuteSlotSize: widget.minuteSlotSize,
startHour: widget.startHour,
endHour: widget.endHour,
timeSlotColorBuilder: widget.timeSlotColorBuilder!,
);
}

@override
Widget build(BuildContext context) {
final filteredDates = _filteredDate();
Expand Down Expand Up @@ -393,6 +417,9 @@ class _InternalMultiDayViewPageState<T extends Object?>
width: widget.width,
child: Stack(
children: [
// Render time slot backgrounds if color builder is provided
if (widget.timeSlotColorBuilder != null)
_buildMultiDayTimeSlotBackgrounds(filteredDates),
CustomPaint(
size: Size(widget.width, widget.height),
painter: widget.hourLinePainter(
Expand Down
9 changes: 9 additions & 0 deletions lib/src/multi_day_view/multi_day_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,10 @@ class MultiDayView<T extends Object?> extends StatefulWidget {
/// Display workday bottom line
final bool showWeekDayBottomLine;

/// A callback that resolves slot background color for each visible time slot.
/// Useful for highlighting unavailable hours, business hours, or blocked time.
final TimeSlotColorBuilder? timeSlotColorBuilder;

/// Main widget for week view.
const MultiDayView({
Key? key,
Expand Down Expand Up @@ -269,6 +273,7 @@ class MultiDayView<T extends Object?> extends StatefulWidget {
this.onTimestampTap,
this.daysInView = 3,
this.showWeekDayBottomLine = true,
this.timeSlotColorBuilder,
}) : assert(!(onHeaderTitleTap != null && weekPageHeaderBuilder != null),
"can't use [onHeaderTitleTap] & [weekPageHeaderBuilder] simultaneously"),
assert((timeLineOffset) >= 0,
Expand Down Expand Up @@ -323,6 +328,8 @@ class MultiDayViewState<T extends Object?> extends State<MultiDayView<T>> {
late HourIndicatorSettings _quarterHourIndicatorSettings;
late DividerSettings _dividerSettings;

late TimeSlotColorBuilder? _timeSlotColorBuilder;

late PageController _pageController;

late DateWidgetBuilder _timeLineBuilder;
Expand Down Expand Up @@ -604,6 +611,7 @@ class MultiDayViewState<T extends Object?> extends State<MultiDayView<T>> {
scrollPhysics: widget.scrollPhysics,
scrollListener: _scrollPageListener,
keepScrollOffset: widget.keepScrollOffset,
timeSlotColorBuilder: _timeSlotColorBuilder,
),
);
},
Expand Down Expand Up @@ -719,6 +727,7 @@ class MultiDayViewState<T extends Object?> extends State<MultiDayView<T>> {
_fullDayEventBuilder =
widget.fullDayEventBuilder ?? _defaultFullDayEventBuilder;
_hourLinePainter = widget.hourLinePainter ?? _defaultHourLinePainter;
_timeSlotColorBuilder = widget.timeSlotColorBuilder;
}

Widget _defaultFullDayEventBuilder(
Expand Down
Loading
Loading