forked from firka/flutter
Added calendar delegate to support custom calendar systems (#161874)
Added `CalendarDelegate` class that supports plugging in custom calendar logics other than Gregorian Calendar System. Here is an example implementation for Nepali(Bikram Sambat) Calendar System: https://github.com/sarbagyastha/nepali_date_picker/blob/m3/lib/src/nepali_calendar_delegate.dart Demo using the `NepaliDatePickerDelegate`: https://date.sarbagyastha.com.np/ Fixes https://github.com/flutter/flutter/issues/77531, https://github.com/flutter/flutter/issues/161873 ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. --------- Co-authored-by: Tong Mu <dkwingsmt@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
83781ae65c
commit
6d6d7914f9
@@ -0,0 +1,148 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Flutter code sample demonstrating how to use a custom [CalendarDelegate]
|
||||
/// with [CalendarDatePicker] to implement a hypothetical calendar system
|
||||
/// where even-numbered months have 21 days, odd-numbered months have 28 days,
|
||||
/// and every month starts on a Monday.
|
||||
|
||||
void main() => runApp(const CalendarDatePickerApp());
|
||||
|
||||
class CalendarDatePickerApp extends StatelessWidget {
|
||||
const CalendarDatePickerApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const MaterialApp(home: CalendarDatePickerExample());
|
||||
}
|
||||
}
|
||||
|
||||
class CalendarDatePickerExample extends StatefulWidget {
|
||||
const CalendarDatePickerExample({super.key});
|
||||
|
||||
@override
|
||||
State<CalendarDatePickerExample> createState() => _CalendarDatePickerExampleState();
|
||||
}
|
||||
|
||||
class _CalendarDatePickerExampleState extends State<CalendarDatePickerExample> {
|
||||
DateTime? selectedDate;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Custom Calendar')),
|
||||
body: Column(
|
||||
spacing: 16,
|
||||
children: <Widget>[
|
||||
CalendarDatePicker(
|
||||
initialDate: DateTime(2025, 2, 8),
|
||||
firstDate: DateTime(2025),
|
||||
lastDate: DateTime(2026),
|
||||
onDateChanged: (DateTime pickedDate) {
|
||||
setState(() {
|
||||
selectedDate = pickedDate;
|
||||
});
|
||||
},
|
||||
calendarDelegate: const CustomCalendarDelegate(),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
Text(
|
||||
selectedDate != null
|
||||
? '${selectedDate!.day}/${selectedDate!.month}/${selectedDate!.year}'
|
||||
: 'No date selected',
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A custom calendar system where even-numbered months have 21 days,
|
||||
/// odd-numbered months have 28 days, and every month starts on a Monday.
|
||||
///
|
||||
/// This hypothetical calendar follows a fixed structure:
|
||||
/// - **Even-numbered months (2, 4, 6, etc.)** always have **21 days**.
|
||||
/// - **Odd-numbered months (1, 3, 5, etc.)** always have **28 days**.
|
||||
/// - **The first day of every month is always a Monday**, ensuring a consistent weekly alignment.
|
||||
class CustomCalendarDelegate extends CalendarDelegate<DateTime> {
|
||||
const CustomCalendarDelegate();
|
||||
|
||||
@override
|
||||
int getDaysInMonth(int year, int month) {
|
||||
return month.isEven ? 21 : 28;
|
||||
}
|
||||
|
||||
@override
|
||||
int firstDayOffset(int year, int month, MaterialLocalizations localizations) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// All the implementations below are based on the Gregorian calendar system.
|
||||
|
||||
@override
|
||||
DateTime now() => DateTime.now();
|
||||
|
||||
@override
|
||||
DateTime dateOnly(DateTime date) => DateUtils.dateOnly(date);
|
||||
|
||||
@override
|
||||
int monthDelta(DateTime startDate, DateTime endDate) => DateUtils.monthDelta(startDate, endDate);
|
||||
|
||||
@override
|
||||
DateTime addMonthsToMonthDate(DateTime monthDate, int monthsToAdd) {
|
||||
return DateUtils.addMonthsToMonthDate(monthDate, monthsToAdd);
|
||||
}
|
||||
|
||||
@override
|
||||
DateTime addDaysToDate(DateTime date, int days) => DateUtils.addDaysToDate(date, days);
|
||||
|
||||
@override
|
||||
DateTime getMonth(int year, int month) => DateTime(year, month);
|
||||
|
||||
@override
|
||||
DateTime getDay(int year, int month, int day) => DateTime(year, month, day);
|
||||
|
||||
@override
|
||||
String formatMonthYear(DateTime date, MaterialLocalizations localizations) {
|
||||
return localizations.formatMonthYear(date);
|
||||
}
|
||||
|
||||
@override
|
||||
String formatMediumDate(DateTime date, MaterialLocalizations localizations) {
|
||||
return localizations.formatMediumDate(date);
|
||||
}
|
||||
|
||||
@override
|
||||
String formatShortMonthDay(DateTime date, MaterialLocalizations localizations) {
|
||||
return localizations.formatShortMonthDay(date);
|
||||
}
|
||||
|
||||
@override
|
||||
String formatShortDate(DateTime date, MaterialLocalizations localizations) {
|
||||
return localizations.formatShortDate(date);
|
||||
}
|
||||
|
||||
@override
|
||||
String formatFullDate(DateTime date, MaterialLocalizations localizations) {
|
||||
return localizations.formatFullDate(date);
|
||||
}
|
||||
|
||||
@override
|
||||
String formatCompactDate(DateTime date, MaterialLocalizations localizations) {
|
||||
return localizations.formatCompactDate(date);
|
||||
}
|
||||
|
||||
@override
|
||||
DateTime? parseCompactDate(String? inputString, MaterialLocalizations localizations) {
|
||||
return localizations.parseCompactDate(inputString);
|
||||
}
|
||||
|
||||
@override
|
||||
String dateHelpText(MaterialLocalizations localizations) {
|
||||
return localizations.dateHelpText;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_api_samples/material/date_picker/custom_calendar_date_picker.0.dart'
|
||||
as example;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
Text getLastDayText(WidgetTester tester) {
|
||||
final Finder dayFinder = find.descendant(of: find.byType(Ink), matching: find.byType(Text));
|
||||
return tester.widget(dayFinder.last);
|
||||
}
|
||||
|
||||
testWidgets('Days are based on the calendar delegate', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(const example.CalendarDatePickerApp());
|
||||
|
||||
final Finder nextMonthButton = find.byIcon(Icons.chevron_right);
|
||||
|
||||
Text lastDayText = getLastDayText(tester);
|
||||
expect(find.text('February 2025'), findsOneWidget);
|
||||
expect(lastDayText.data, equals('21'));
|
||||
|
||||
await tester.tap(nextMonthButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
lastDayText = getLastDayText(tester);
|
||||
expect(find.text('March 2025'), findsOneWidget);
|
||||
expect(lastDayText.data, equals('28'));
|
||||
|
||||
await tester.tap(nextMonthButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
lastDayText = getLastDayText(tester);
|
||||
expect(find.text('April 2025'), findsOneWidget);
|
||||
expect(lastDayText.data, equals('21'));
|
||||
|
||||
await tester.tap(nextMonthButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
lastDayText = getLastDayText(tester);
|
||||
expect(lastDayText.data, equals('28'));
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user