Skip to content

Commit e4773f9

Browse files
✨ Replace CalendarThemeProvider with ThemeData.extensions and unify color palette
Removes CalendarThemeProvider and CalendarThemeData in favor of standard ThemeData.extensions, unifies all color tokens into a single CalendarViewColors palette (with matched light/dark sub-palettes), and makes all five BuildContext theme accessors brightness-aware. Adds ScheduleView theming, updates the example app to drive all calendar and chrome colors from a single AppColors source of truth, and introduces runtime theme-mode switching via ThemeController.
1 parent dc7f19b commit e4773f9

32 files changed

Lines changed: 822 additions & 628 deletions

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# [Unreleased - 29 May 2026]
22

3-
- [BREAKING] Added `BuildContextExtension` to public API for theme access.
3+
- [BREAKING] Removed `CalendarThemeProvider` and `CalendarThemeData`. Themes are resolved exclusively from `ThemeData.extensions`.
4+
- [BREAKING] Added `BuildContextExtension` to public API for theme access; themes now fallback to system theme (brightness-aware) when no extension is registered.
5+
- [BREAKING] Removed `BuildContextMultiDayViewThemeExtension.multiDayViewTheme` in favor of `BuildContextExtension.multiDayViewColors`.
6+
- [BREAKING] `highlightColor` was renamed to `cellHighlightColor` in `MonthViewThemeData.copyWith` method for API consistency.
47
- Added `ScheduleView`, a scrollable, agenda-style calendar that groups events by day and month. [#101](https://github.com/SimformSolutionsPvtLtd/flutter_calendar_view/issues/101)
58
- Added localized `months` and `monthsAbbr` fields to `CalendarLocalizations`, with the `DateTime.getMonthName({abbreviated})` and `DateTime.getMonthYear({abbreviatedMonth})` extensions.
69
- [BREAKING] Changed `RecurrenceSettings.weekdays` type from `List<int>` to `List<WeekDays>`. [#509](https://github.com/SimformSolutionsPvtLtd/flutter_calendar_view/issues/509)

doc/documentation.md

Lines changed: 98 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,46 +1021,48 @@ To customise `MonthView`, `DayView`, `WeekView` & `MultiDayView` page header use
10211021
),
10221022
```
10231023

1024-
### Theme implementation approaches
1024+
### Theme implementation approach
10251025

1026-
There are two main ways to customize the theme for calendar views:
1026+
Every theme data class extends `ThemeExtension`, so themes are applied by
1027+
registering them in `ThemeData.extensions`. Each calendar view reads its theme
1028+
from the nearest `ThemeData` (via `context.dayViewColors`, `context.weekViewColors`,
1029+
etc.), falling back to `.light()` / `.dark()` based on `Theme.of(context).brightness`.
10271030

1028-
1. **Using `CalendarThemeProvider`** (recommended):
1029-
```dart
1030-
CalendarThemeProvider(
1031-
calendarTheme: CalendarThemeData(
1032-
monthViewTheme: MonthViewThemeData.light().copyWith(
1033-
cellInMonthColor: Colors.blue.shade50,
1034-
cellBorderColor: Colors.blue.shade300,
1035-
),
1036-
dayViewTheme: DayViewThemeData.light(),
1037-
weekViewTheme: WeekViewThemeData.light(),
1038-
multiDayViewTheme: MultiDayViewThemeData.light(),
1039-
scheduleViewTheme: ScheduleViewThemeData.light(),
1040-
),
1041-
child: YourApp(),
1042-
)
1043-
```
1031+
```dart
1032+
// Create custom theme
1033+
final myMonthViewTheme = MonthViewThemeData.light().copyWith(
1034+
cellInMonthColor: Colors.blue.shade50,
1035+
cellBorderColor: Colors.blue.shade300,
1036+
);
10441037
1045-
2. **Using ThemeData extensions** (since all theme data classes extend `ThemeExtension`):
1046-
```dart
1047-
// Create custom theme
1048-
final myMonthViewTheme = MonthViewThemeData.light().copyWith(
1049-
cellInMonthColor: Colors.blue.shade50,
1050-
cellBorderColor: Colors.blue.shade300,
1051-
);
1038+
// Apply to your app theme — register extensions on BOTH theme and darkTheme
1039+
// so customizations apply in all brightness modes.
1040+
final lightTheme = ThemeData.light().copyWith(
1041+
extensions: [
1042+
myMonthViewTheme,
1043+
DayViewThemeData.light(),
1044+
WeekViewThemeData.light(),
1045+
MultiDayViewThemeData.light(),
1046+
ScheduleViewThemeData.light(),
1047+
],
1048+
);
10521049
1053-
// Apply to your app theme
1054-
final theme = ThemeData.light().copyWith(
1055-
extensions: [
1056-
myMonthViewTheme,
1057-
DayViewThemeData.light(),
1058-
WeekViewThemeData.light(),
1059-
MultiDayViewThemeData.light(),
1060-
ScheduleViewThemeData.light(),
1061-
],
1062-
);
1063-
```
1050+
final darkTheme = ThemeData.dark().copyWith(
1051+
extensions: [
1052+
MonthViewThemeData.dark(),
1053+
DayViewThemeData.dark(),
1054+
WeekViewThemeData.dark(),
1055+
MultiDayViewThemeData.dark(),
1056+
ScheduleViewThemeData.dark(),
1057+
],
1058+
);
1059+
1060+
MaterialApp(
1061+
theme: lightTheme,
1062+
darkTheme: darkTheme,
1063+
// ...
1064+
);
1065+
```
10641066

10651067
### Day view
10661068
* Default timeline text color is `timelineTextColor` in `DayViewThemeData` (defaults to `onSurface`).
@@ -1209,19 +1211,19 @@ Hide divider in multiday view.
12091211
* In `ScheduleDateLayout.top`, the divider beneath the default date header uses `dateDividerColor`; override it per-widget with `defaultDateHeaderDividerSettings` (use `DividerSettings.none()` to hide it).
12101212

12111213
```dart
1212-
CalendarThemeProvider(
1213-
calendarTheme: CalendarThemeData(
1214-
monthViewTheme: MonthViewThemeData.light(),
1215-
dayViewTheme: DayViewThemeData.light(),
1216-
weekViewTheme: WeekViewThemeData.light(),
1217-
multiDayViewTheme: MultiDayViewThemeData.light(),
1218-
scheduleViewTheme: ScheduleViewThemeData.light().copyWith(
1214+
final theme = ThemeData.light().copyWith(
1215+
extensions: [
1216+
ScheduleViewThemeData.light().copyWith(
12191217
todayHighlightColor: Colors.blue,
12201218
eventTileAlpha: 40,
12211219
),
1222-
),
1223-
child: YourApp(),
1224-
)
1220+
],
1221+
);
1222+
1223+
MaterialApp(
1224+
theme: theme,
1225+
// ...
1226+
);
12251227
```
12261228

12271229
# Migration Guides
@@ -1382,6 +1384,56 @@ final end = organized.endDuration; // TimeOfDay
13821384
| `DateTime.dateYMD` (Deprecated) | Use `DateTime.withoutTime` getter |
13831385
| `MaterialColorExtension.accent` (Deprecated) | No replacement |
13841386

1387+
### 10. Removed `CalendarThemeProvider` and `CalendarThemeData`
1388+
1389+
`CalendarThemeProvider` and its `CalendarThemeData` payload have been removed. The calendar views never read from them — themes resolve exclusively from `ThemeData.extensions` (via `context.dayViewColors`, `context.scheduleViewColors`, etc.). Register each view's `…ViewThemeData` in `ThemeData.extensions` instead.
1390+
1391+
**Before (≤ 2.x):**
1392+
```dart
1393+
CalendarThemeProvider(
1394+
calendarTheme: CalendarThemeData(
1395+
monthViewTheme: MonthViewThemeData.light(),
1396+
dayViewTheme: DayViewThemeData.light().copyWith(hourLineColor: Colors.grey),
1397+
weekViewTheme: WeekViewThemeData.light(),
1398+
multiDayViewTheme: MultiDayViewThemeData.light(),
1399+
scheduleViewTheme: ScheduleViewThemeData.light(),
1400+
),
1401+
child: MaterialApp(
1402+
// ...
1403+
),
1404+
);
1405+
```
1406+
1407+
**After (3.0.0+):**
1408+
```dart
1409+
MaterialApp(
1410+
theme: ThemeData.light().copyWith(
1411+
extensions: [
1412+
MonthViewThemeData.light(),
1413+
DayViewThemeData.light().copyWith(hourLineColor: Colors.grey),
1414+
WeekViewThemeData.light(),
1415+
MultiDayViewThemeData.light(),
1416+
ScheduleViewThemeData.light(),
1417+
],
1418+
),
1419+
// ...
1420+
);
1421+
```
1422+
1423+
### 11. Removed `BuildContextMultiDayViewThemeExtension.multiDayViewTheme`
1424+
1425+
The standalone `context.multiDayViewTheme` accessor (from the `BuildContextMultiDayViewThemeExtension` extension) has been removed. Use `context.multiDayViewColors` from `BuildContextExtension` instead — it returns the same `MultiDayViewThemeData`, resolving from `ThemeData.extensions` and falling back to `.light()` / `.dark()` based on the ambient `Theme` brightness.
1426+
1427+
**Before (≤ 2.x):**
1428+
```dart
1429+
final theme = context.multiDayViewTheme;
1430+
```
1431+
1432+
**After (3.0.0+):**
1433+
```dart
1434+
final theme = context.multiDayViewColors;
1435+
```
1436+
13851437

13861438
## Migrate from `1.x.x` to latest
13871439
### ⚠ Note

example/lib/constants.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class AppConstants {
99

1010
static OutlineInputBorder inputBorder = OutlineInputBorder(
1111
borderRadius: BorderRadius.circular(7),
12-
borderSide: BorderSide(width: 2, color: AppColors.outlineVariant),
12+
borderSide: BorderSide(width: 2, color: AppColors.light.outlineVariant),
1313
);
1414

1515
static InputDecoration get inputDecoration => InputDecoration(

example/lib/extension.dart

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,13 @@ extension TimeOfDayExtension on TimeOfDay {
113113
}
114114

115115
extension BuildContextExtension on BuildContext {
116-
AppThemeExtension get appColors =>
117-
Theme.of(this).extension<AppThemeExtension>() ??
118-
AppThemeExtension.light();
116+
AppThemeExtension get appColors {
117+
final theme = Theme.of(this);
118+
return theme.extension<AppThemeExtension>() ??
119+
(theme.brightness == Brightness.dark
120+
? AppThemeExtension.dark()
121+
: AppThemeExtension.light());
122+
}
119123
}
120124

121125
extension LocalizedDateExtension on DateTime {

example/lib/l10n/app_ar.arb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@
2727
"createNewEvent": "إنشاء حدث جديد",
2828
"updateEvent": "تحديث الحدث",
2929
"activeView": "العرض النشط:",
30-
"darkMode": "الوضع المظلم:",
30+
"theme": "السمة",
31+
"themeSystem": "النظام",
32+
"themeLight": "فاتح",
33+
"themeDark": "داكن",
3134
"eventTitle": "عنوان الحدث",
3235
"pleaseEnterEventTitle": "الرجاء إدخال عنوان الحدث.",
3336
"recurringEvent": "حدث متكرر",

example/lib/l10n/app_en.arb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@
2727
"createNewEvent": "Create New Event",
2828
"updateEvent": "Update Event",
2929
"activeView": "Active View:",
30-
"darkMode": "Dark mode:",
30+
"theme": "Theme",
31+
"themeSystem": "System",
32+
"themeLight": "Light",
33+
"themeDark": "Dark",
3134
"eventTitle": "Event Title",
3235
"pleaseEnterEventTitle": "Please enter event title.",
3336
"recurringEvent": "Recurring Event",

example/lib/l10n/app_es.arb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@
2727
"createNewEvent": "Crear nuevo evento",
2828
"updateEvent": "Actualizar evento",
2929
"activeView": "Vista activa:",
30-
"darkMode": "Modo oscuro:",
30+
"theme": "Tema",
31+
"themeSystem": "Sistema",
32+
"themeLight": "Claro",
33+
"themeDark": "Oscuro",
3134
"eventTitle": "Título del Evento",
3235
"pleaseEnterEventTitle": "Por favor, introduce el título del evento.",
3336
"recurringEvent": "Evento Recurrente",

example/lib/main.dart

Lines changed: 30 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import 'l10n/app_localizations.dart';
99
import 'localization/calendar_locales.dart';
1010
import 'localization/locale_controller.dart';
1111
import 'pages/home_page.dart';
12-
import 'theme/app_colors.dart';
12+
import 'theme/theme_controller.dart';
1313

1414
void main() {
1515
runApp(MyApp());
@@ -21,53 +21,46 @@ class MyApp extends StatefulWidget {
2121
}
2222

2323
class _MyAppState extends State<MyApp> {
24-
bool isDarkMode = false;
2524
final String initialLocale = 'en';
2625

26+
/// Created once and kept stable for the app's lifetime so theme/locale
27+
/// changes never recreate the controller (which would re-seed all events).
28+
final _controller = EventController();
29+
final _themeMode = ValueNotifier<ThemeMode>(ThemeMode.system);
30+
2731
@override
2832
void initState() {
2933
super.initState();
3034
CalendarLocales.initialize();
3135
}
3236

37+
@override
38+
void dispose() {
39+
_controller.dispose();
40+
_themeMode.dispose();
41+
super.dispose();
42+
}
43+
3344
// This widget is the root of your application.
3445
@override
3546
Widget build(BuildContext context) {
36-
return LocaleController(
37-
initialLocale: PackageStrings.selectedLocale,
38-
child: Builder(
39-
builder: (context) {
40-
final localeController = LocaleController.of(context);
41-
return CalendarThemeProvider(
42-
calendarTheme: CalendarThemeData(
43-
monthViewTheme: isDarkMode
44-
? MonthViewThemeData.dark()
45-
: MonthViewThemeData.light(),
46-
dayViewTheme: isDarkMode
47-
? DayViewThemeData.dark()
48-
: DayViewThemeData.light().copyWith(
49-
hourLineColor: AppColors.primary,
50-
)
51-
as DayViewThemeData,
52-
weekViewTheme: isDarkMode
53-
? WeekViewThemeData.dark()
54-
: WeekViewThemeData.light(),
55-
multiDayViewTheme: isDarkMode
56-
? MultiDayViewThemeData.dark()
57-
: MultiDayViewThemeData.light(),
58-
scheduleViewTheme: isDarkMode
59-
? ScheduleViewThemeData.dark()
60-
: ScheduleViewThemeData.light(),
61-
),
62-
child: CalendarControllerProvider(
63-
controller: EventController(),
64-
child: MaterialApp(
47+
return CalendarControllerProvider(
48+
controller: _controller,
49+
child: ThemeController(
50+
notifier: _themeMode,
51+
child: LocaleController(
52+
initialLocale: PackageStrings.selectedLocale,
53+
child: Builder(
54+
builder: (context) {
55+
final localeController = LocaleController.of(context);
56+
final themeController = ThemeController.of(context);
57+
return MaterialApp(
6558
title: 'Flutter Calendar Page Demo',
6659
debugShowCheckedModeBanner: false,
6760
locale: Locale(localeController.currentLocale),
6861
theme: AppTheme.light,
6962
darkTheme: AppTheme.dark,
70-
themeMode: isDarkMode ? ThemeMode.dark : ThemeMode.light,
63+
themeMode: themeController.themeMode,
7164
localizationsDelegates: [
7265
AppLocalizations.delegate,
7366
GlobalMaterialLocalizations.delegate,
@@ -86,14 +79,11 @@ class _MyAppState extends State<MyApp> {
8679
PointerDeviceKind.touch,
8780
},
8881
),
89-
home: HomePage(
90-
onChangeTheme: (isDark) =>
91-
setState(() => isDarkMode = isDark),
92-
),
93-
),
94-
),
95-
);
96-
},
82+
home: const HomePage(),
83+
);
84+
},
85+
),
86+
),
9787
),
9888
);
9989
}

example/lib/pages/home_page.dart

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,7 @@ import 'web/web_home_page.dart';
99
DateTime get _now => DateTime.now();
1010

1111
class HomePage extends StatefulWidget {
12-
const HomePage({this.onChangeTheme, super.key});
13-
14-
/// Return true for dark mode
15-
/// false for light mode
16-
final void Function(bool)? onChangeTheme;
12+
const HomePage({super.key});
1713

1814
@override
1915
State<HomePage> createState() => _HomePageState();
@@ -352,8 +348,8 @@ class _HomePageState extends State<HomePage> {
352348
@override
353349
Widget build(BuildContext context) {
354350
return ResponsiveWidget(
355-
mobileWidget: MobileHomePage(onChangeTheme: widget.onChangeTheme),
356-
webWidget: WebHomePage(onThemeChange: widget.onChangeTheme),
351+
mobileWidget: MobileHomePage(),
352+
webWidget: WebHomePage(),
357353
);
358354
}
359355
}

0 commit comments

Comments
 (0)