|
|
|
@ -56,8 +56,9 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
|
|
|
|
late DateRangeSelectorRangeViewModel model;
|
|
|
|
late DateRangeSelectorRangeViewModel model;
|
|
|
|
PickerViewMode _viewMode = PickerViewMode.date;
|
|
|
|
PickerViewMode _viewMode = PickerViewMode.date;
|
|
|
|
|
|
|
|
|
|
|
|
// Cache for Hijri conversions to avoid repeated calculations
|
|
|
|
// Prevent Syncfusion's initial onViewChanged callback from scheduling an
|
|
|
|
final Map<String, HijriGregDate> _hijriCache = {};
|
|
|
|
// unnecessary second build of the same month.
|
|
|
|
|
|
|
|
late int _displayedMonthKey;
|
|
|
|
|
|
|
|
|
|
|
|
// Track the current Hijri month/year being displayed (for header display accuracy)
|
|
|
|
// Track the current Hijri month/year being displayed (for header display accuracy)
|
|
|
|
HijriGregDate? _currentHijriDisplay;
|
|
|
|
HijriGregDate? _currentHijriDisplay;
|
|
|
|
@ -65,10 +66,14 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
|
|
|
|
@override
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
void initState() {
|
|
|
|
_calendarController = DateRangePickerController();
|
|
|
|
_calendarController = DateRangePickerController();
|
|
|
|
|
|
|
|
final today = DateTime.now();
|
|
|
|
|
|
|
|
_displayedMonthKey = _monthKey(today);
|
|
|
|
|
|
|
|
if (widget.designType == CalendarDesignType.designV2) {
|
|
|
|
|
|
|
|
_calendarController.displayDate = today;
|
|
|
|
|
|
|
|
}
|
|
|
|
scheduleMicrotask(() {
|
|
|
|
scheduleMicrotask(() {
|
|
|
|
if (widget.designType == CalendarDesignType.designV2) {
|
|
|
|
if (widget.designType == CalendarDesignType.designV2) {
|
|
|
|
// For V2, select today's date by default
|
|
|
|
// For V2, select today's date by default
|
|
|
|
final today = DateTime.now();
|
|
|
|
|
|
|
|
_calendarController.selectedDate = today;
|
|
|
|
_calendarController.selectedDate = today;
|
|
|
|
model.updateSelectedDate(today);
|
|
|
|
model.updateSelectedDate(today);
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
@ -83,7 +88,9 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
model = Provider.of<DateRangeSelectorRangeViewModel>(context);
|
|
|
|
model = Provider.of<DateRangeSelectorRangeViewModel>(context);
|
|
|
|
|
|
|
|
|
|
|
|
_calendarController.selectedRange = PickerDateRange(model.fromDate, model.toDate);
|
|
|
|
if (widget.designType != CalendarDesignType.designV2) {
|
|
|
|
|
|
|
|
_calendarController.selectedRange = PickerDateRange(model.fromDate, model.toDate);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return widget.designType == CalendarDesignType.designV2 ? _buildDesignV2(widget.btnTitle) : _buildDefaultUI();
|
|
|
|
return widget.designType == CalendarDesignType.designV2 ? _buildDesignV2(widget.btnTitle) : _buildDefaultUI();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -273,18 +280,17 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
|
|
|
|
tabs: [CustomTabBarModel(null, LocaleKeys.gregorianDate.tr()), CustomTabBarModel(null, LocaleKeys.hijriDate.tr())],
|
|
|
|
tabs: [CustomTabBarModel(null, LocaleKeys.gregorianDate.tr()), CustomTabBarModel(null, LocaleKeys.hijriDate.tr())],
|
|
|
|
onTabChange: (index) {
|
|
|
|
onTabChange: (index) {
|
|
|
|
final calendarModel = Provider.of<DateRangCalenderModel>(context, listen: false);
|
|
|
|
final calendarModel = Provider.of<DateRangCalenderModel>(context, listen: false);
|
|
|
|
|
|
|
|
if (calendarModel.getSelectedTabIndex == index) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Update local fields before notifying the provider so
|
|
|
|
|
|
|
|
// the calendar changes with a single rebuild.
|
|
|
|
|
|
|
|
_viewMode = PickerViewMode.date;
|
|
|
|
|
|
|
|
_currentHijriDisplay = null;
|
|
|
|
calendarModel.setTabIndex(index);
|
|
|
|
calendarModel.setTabIndex(index);
|
|
|
|
|
|
|
|
|
|
|
|
// Notify parent widget about calendar type change (e.g., AuthenticationViewModel)
|
|
|
|
// Notify parent widget about calendar type change (e.g., AuthenticationViewModel)
|
|
|
|
final isGregorian = index == 0;
|
|
|
|
final isGregorian = index == 0;
|
|
|
|
widget.onCalendarTypeChanged?.call(isGregorian);
|
|
|
|
widget.onCalendarTypeChanged?.call(isGregorian);
|
|
|
|
print('📅 Calendar type changed: ${isGregorian ? "Gregorian" : "Hijri"}');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Reset view mode and Hijri display when switching calendar types
|
|
|
|
|
|
|
|
setState(() {
|
|
|
|
|
|
|
|
_viewMode = PickerViewMode.date;
|
|
|
|
|
|
|
|
_currentHijriDisplay = null;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
},
|
|
|
|
},
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
@ -306,12 +312,17 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
|
|
|
|
child: Material(
|
|
|
|
child: Material(
|
|
|
|
color: AppColors.whiteColor,
|
|
|
|
color: AppColors.whiteColor,
|
|
|
|
// Rebuild when view mode or calendar type changes
|
|
|
|
// Rebuild when view mode or calendar type changes
|
|
|
|
|
|
|
|
// Syncfusion caches its rendered date cells. Include
|
|
|
|
|
|
|
|
// the calendar type in the key so Gregorian/Hijri day
|
|
|
|
|
|
|
|
// labels refresh immediately when the tab changes.
|
|
|
|
|
|
|
|
// Hijri conversion is now constant-time, so this
|
|
|
|
|
|
|
|
// targeted recreation remains responsive.
|
|
|
|
key: ValueKey('${calendarModel.getSelectedTabIndex}-$_viewMode'),
|
|
|
|
key: ValueKey('${calendarModel.getSelectedTabIndex}-$_viewMode'),
|
|
|
|
child: _viewMode == PickerViewMode.month
|
|
|
|
child: _viewMode == PickerViewMode.month
|
|
|
|
? _buildMonthPicker(isArabic, calendarModel)
|
|
|
|
? _buildMonthPicker(isArabic, calendarModel)
|
|
|
|
: _viewMode == PickerViewMode.year
|
|
|
|
: _viewMode == PickerViewMode.year
|
|
|
|
? _buildYearPicker(isArabic, calendarModel)
|
|
|
|
? _buildYearPicker(isArabic, calendarModel)
|
|
|
|
: (calendarModel.isHijri ? _buildHijriCalendar(isArabic) : _buildGregorianCalendar(isArabic)),
|
|
|
|
: (calendarModel.isHijri ? _buildHijriCalendar(isArabic) : _buildGregorianCalendar(isArabic)),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
],
|
|
|
|
@ -343,6 +354,8 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
|
|
|
|
|
|
|
|
|
|
|
|
// Build Gregorian calendar (Design V2 - Single Date Selection)
|
|
|
|
// Build Gregorian calendar (Design V2 - Single Date Selection)
|
|
|
|
Widget _buildGregorianCalendar(bool isArabic) {
|
|
|
|
Widget _buildGregorianCalendar(bool isArabic) {
|
|
|
|
|
|
|
|
final today = DateTime.now();
|
|
|
|
|
|
|
|
|
|
|
|
return SfDateRangePicker(
|
|
|
|
return SfDateRangePicker(
|
|
|
|
controller: _calendarController,
|
|
|
|
controller: _calendarController,
|
|
|
|
selectionMode: DateRangePickerSelectionMode.single,
|
|
|
|
selectionMode: DateRangePickerSelectionMode.single,
|
|
|
|
@ -366,7 +379,7 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
|
|
|
|
// Custom cell builder for square border selection
|
|
|
|
// Custom cell builder for square border selection
|
|
|
|
cellBuilder: (BuildContext context, DateRangePickerCellDetails cellDetails) {
|
|
|
|
cellBuilder: (BuildContext context, DateRangePickerCellDetails cellDetails) {
|
|
|
|
if (cellDetails.date != DateTime(0)) {
|
|
|
|
if (cellDetails.date != DateTime(0)) {
|
|
|
|
final isToday = cellDetails.date.day == DateTime.now().day && cellDetails.date.month == DateTime.now().month && cellDetails.date.year == DateTime.now().year;
|
|
|
|
final isToday = cellDetails.date.day == today.day && cellDetails.date.month == today.month && cellDetails.date.year == today.year;
|
|
|
|
final isSelected = _calendarController.selectedDate != null &&
|
|
|
|
final isSelected = _calendarController.selectedDate != null &&
|
|
|
|
cellDetails.date.day == _calendarController.selectedDate!.day &&
|
|
|
|
cellDetails.date.day == _calendarController.selectedDate!.day &&
|
|
|
|
cellDetails.date.month == _calendarController.selectedDate!.month &&
|
|
|
|
cellDetails.date.month == _calendarController.selectedDate!.month &&
|
|
|
|
@ -414,12 +427,7 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
onViewChanged: (DateRangePickerViewChangedArgs args) {
|
|
|
|
onViewChanged: (DateRangePickerViewChangedArgs args) {
|
|
|
|
// Trigger rebuild when month changes
|
|
|
|
_handleCalendarViewChanged(args);
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
|
|
|
|
|
|
if (mounted) {
|
|
|
|
|
|
|
|
setState(() {});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
},
|
|
|
|
},
|
|
|
|
onSelectionChanged: (DateRangePickerSelectionChangedArgs args) {
|
|
|
|
onSelectionChanged: (DateRangePickerSelectionChangedArgs args) {
|
|
|
|
if (args.value is DateTime) {
|
|
|
|
if (args.value is DateTime) {
|
|
|
|
@ -433,105 +441,155 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Build Hijri calendar (Design V2 - Single Date Selection)
|
|
|
|
// Build one complete Hijri month instead of relabelling the dates in a
|
|
|
|
// Note: SfDateRangePicker doesn't have built-in Hijri support
|
|
|
|
// Gregorian month. A Gregorian month overlaps two Hijri months, which would
|
|
|
|
// Using Gregorian calendar with Hijri date conversion in the model
|
|
|
|
// otherwise produce sequences such as 16...30, 1...17 in the same grid.
|
|
|
|
Widget _buildHijriCalendar(bool isArabic) {
|
|
|
|
Widget _buildHijriCalendar(bool isArabic) {
|
|
|
|
final calendarModel = Provider.of<DateRangCalenderModel>(context, listen: false);
|
|
|
|
final calendarModel = Provider.of<DateRangCalenderModel>(context, listen: false);
|
|
|
|
|
|
|
|
final today = DateTime.now();
|
|
|
|
|
|
|
|
final displayedGregorian = _calendarController.displayDate ?? today;
|
|
|
|
|
|
|
|
final displayedHijri = _currentHijriDisplay ?? calendarModel.gregorianToHijri(displayedGregorian);
|
|
|
|
|
|
|
|
final firstHijriDay = HijriGregDate(day: 1, month: displayedHijri.month, year: displayedHijri.year);
|
|
|
|
|
|
|
|
final firstGregorianDay = calendarModel.hijriToGregorian(firstHijriDay);
|
|
|
|
|
|
|
|
final leadingEmptyCells = firstGregorianDay.weekday % DateTime.daysPerWeek;
|
|
|
|
|
|
|
|
final daysInMonth = calendarModel.getDaysInMonth(displayedHijri.year, displayedHijri.month);
|
|
|
|
|
|
|
|
final selectedDate = _calendarController.selectedDate;
|
|
|
|
|
|
|
|
const weekdays = [
|
|
|
|
|
|
|
|
DateTime.sunday,
|
|
|
|
|
|
|
|
DateTime.monday,
|
|
|
|
|
|
|
|
DateTime.tuesday,
|
|
|
|
|
|
|
|
DateTime.wednesday,
|
|
|
|
|
|
|
|
DateTime.thursday,
|
|
|
|
|
|
|
|
DateTime.friday,
|
|
|
|
|
|
|
|
DateTime.saturday,
|
|
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
return SfDateRangePicker(
|
|
|
|
return Column(
|
|
|
|
controller: _calendarController,
|
|
|
|
children: [
|
|
|
|
selectionMode: DateRangePickerSelectionMode.single,
|
|
|
|
SizedBox(
|
|
|
|
showNavigationArrow: false,
|
|
|
|
height: 32.h,
|
|
|
|
headerHeight: 0,
|
|
|
|
child: Row(
|
|
|
|
backgroundColor: AppColors.whiteColor,
|
|
|
|
children: weekdays
|
|
|
|
monthViewSettings: DateRangePickerMonthViewSettings(
|
|
|
|
.map(
|
|
|
|
viewHeaderStyle: DateRangePickerViewHeaderStyle(
|
|
|
|
(weekday) => Expanded(
|
|
|
|
backgroundColor: AppColors.whiteColor,
|
|
|
|
child: Center(
|
|
|
|
textStyle: TextStyle(
|
|
|
|
child: Text(
|
|
|
|
fontSize: 12.f,
|
|
|
|
calendarModel.getWeekdayNameLocalized(weekday, isArabic),
|
|
|
|
fontWeight: FontWeight.w600,
|
|
|
|
style: TextStyle(
|
|
|
|
letterSpacing: -0.46,
|
|
|
|
fontSize: 12.f,
|
|
|
|
color: AppColors.textColor,
|
|
|
|
fontWeight: FontWeight.w600,
|
|
|
|
|
|
|
|
letterSpacing: -0.46,
|
|
|
|
|
|
|
|
color: AppColors.textColor,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
.toList(),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
showTrailingAndLeadingDates: false,
|
|
|
|
Expanded(
|
|
|
|
dayFormat: "EEE",
|
|
|
|
child: GridView.builder(
|
|
|
|
),
|
|
|
|
padding: EdgeInsets.zero,
|
|
|
|
cellBuilder: (BuildContext context, DateRangePickerCellDetails cellDetails) {
|
|
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
|
|
if (cellDetails.date != DateTime(0)) {
|
|
|
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
|
|
|
// Use cached Hijri conversion
|
|
|
|
crossAxisCount: DateTime.daysPerWeek,
|
|
|
|
final dateKey = '${cellDetails.date.year}-${cellDetails.date.month}-${cellDetails.date.day}';
|
|
|
|
mainAxisExtent: 36.h,
|
|
|
|
final hijriDate = _hijriCache.putIfAbsent(dateKey, () => calendarModel.gregorianToHijri(cellDetails.date));
|
|
|
|
mainAxisSpacing: 4.h,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
itemCount: 42,
|
|
|
|
|
|
|
|
itemBuilder: (context, index) {
|
|
|
|
|
|
|
|
final hijriDay = index - leadingEmptyCells + 1;
|
|
|
|
|
|
|
|
if (hijriDay < 1 || hijriDay > daysInMonth) {
|
|
|
|
|
|
|
|
return const SizedBox.shrink();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
final isToday = cellDetails.date.day == DateTime.now().day && cellDetails.date.month == DateTime.now().month && cellDetails.date.year == DateTime.now().year;
|
|
|
|
final hijriDate = HijriGregDate(day: hijriDay, month: displayedHijri.month, year: displayedHijri.year);
|
|
|
|
final isSelected = _calendarController.selectedDate != null &&
|
|
|
|
final gregorianDate = calendarModel.hijriToGregorian(hijriDate);
|
|
|
|
cellDetails.date.day == _calendarController.selectedDate!.day &&
|
|
|
|
final isToday = _isSameDate(gregorianDate, today);
|
|
|
|
cellDetails.date.month == _calendarController.selectedDate!.month &&
|
|
|
|
final isSelected = selectedDate != null && _isSameDate(gregorianDate, selectedDate);
|
|
|
|
cellDetails.date.year == _calendarController.selectedDate!.year;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return Container(
|
|
|
|
return GestureDetector(
|
|
|
|
alignment: Alignment.center,
|
|
|
|
behavior: HitTestBehavior.opaque,
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
onTap: () {
|
|
|
|
borderRadius: BorderRadius.circular(8.h),
|
|
|
|
_calendarController.selectedDate = gregorianDate;
|
|
|
|
border: isSelected ? Border.all(color: AppColors.primaryRedColor, width: 2) : null,
|
|
|
|
setState(() {
|
|
|
|
color: Colors.transparent,
|
|
|
|
start = gregorianDate;
|
|
|
|
),
|
|
|
|
end = gregorianDate;
|
|
|
|
child: Text(
|
|
|
|
});
|
|
|
|
hijriDate.day.toString(),
|
|
|
|
model.updateSelectedDate(gregorianDate);
|
|
|
|
style: TextStyle(
|
|
|
|
},
|
|
|
|
fontFamily: "Poppins",
|
|
|
|
child: Container(
|
|
|
|
fontSize: 12.f,
|
|
|
|
alignment: Alignment.center,
|
|
|
|
color: AppColors.textColor,
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
fontWeight: isToday ? FontWeight.bold : (isSelected ? FontWeight.w600 : FontWeight.normal),
|
|
|
|
borderRadius: BorderRadius.circular(8.h),
|
|
|
|
),
|
|
|
|
border: isSelected ? Border.all(color: AppColors.primaryRedColor, width: 2) : null,
|
|
|
|
),
|
|
|
|
color: Colors.transparent,
|
|
|
|
);
|
|
|
|
),
|
|
|
|
}
|
|
|
|
child: Text(
|
|
|
|
return Container();
|
|
|
|
hijriDay.toString(),
|
|
|
|
},
|
|
|
|
style: TextStyle(
|
|
|
|
selectionShape: DateRangePickerSelectionShape.rectangle,
|
|
|
|
fontFamily: "Poppins",
|
|
|
|
selectionRadius: 8.h,
|
|
|
|
fontSize: 12.f,
|
|
|
|
selectionColor: Colors.transparent,
|
|
|
|
color: AppColors.textColor,
|
|
|
|
selectionTextStyle: TextStyle(
|
|
|
|
fontWeight: isToday ? FontWeight.bold : (isSelected ? FontWeight.w600 : FontWeight.normal),
|
|
|
|
fontFamily: "Poppins",
|
|
|
|
),
|
|
|
|
color: AppColors.textColor,
|
|
|
|
),
|
|
|
|
fontWeight: FontWeight.w600,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
todayHighlightColor: Colors.transparent,
|
|
|
|
},
|
|
|
|
monthCellStyle: DateRangePickerMonthCellStyle(
|
|
|
|
),
|
|
|
|
textStyle: TextStyle(
|
|
|
|
|
|
|
|
fontFamily: "Poppins",
|
|
|
|
|
|
|
|
fontSize: 12.f,
|
|
|
|
|
|
|
|
color: AppColors.textColor,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
todayTextStyle: TextStyle(
|
|
|
|
|
|
|
|
fontFamily: "Poppins",
|
|
|
|
|
|
|
|
color: AppColors.textColor,
|
|
|
|
|
|
|
|
fontWeight: FontWeight.bold,
|
|
|
|
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
onViewChanged: (DateRangePickerViewChangedArgs args) {
|
|
|
|
|
|
|
|
// Clear cache when month changes to avoid stale data
|
|
|
|
|
|
|
|
_hijriCache.clear();
|
|
|
|
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
|
|
|
|
|
|
if (mounted) {
|
|
|
|
|
|
|
|
setState(() {});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
onSelectionChanged: (DateRangePickerSelectionChangedArgs args) {
|
|
|
|
|
|
|
|
if (args.value is DateTime) {
|
|
|
|
|
|
|
|
setState(() {
|
|
|
|
|
|
|
|
start = args.value;
|
|
|
|
|
|
|
|
end = args.value;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
model.updateSelectedDate(args.value);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool _isSameDate(DateTime first, DateTime second) {
|
|
|
|
|
|
|
|
return first.year == second.year && first.month == second.month && first.day == second.day;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int _monthKey(DateTime date) => (date.year * 100) + date.month;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void _changeHijriMonth(DateRangCalenderModel calendarModel, int monthDelta) {
|
|
|
|
|
|
|
|
final displayedDate = _calendarController.displayDate ?? DateTime.now();
|
|
|
|
|
|
|
|
final currentHijri = _currentHijriDisplay ?? calendarModel.gregorianToHijri(displayedDate);
|
|
|
|
|
|
|
|
final zeroBasedMonth = (currentHijri.year * 12) + currentHijri.month - 1 + monthDelta;
|
|
|
|
|
|
|
|
final nextHijriDate = HijriGregDate(
|
|
|
|
|
|
|
|
day: 1,
|
|
|
|
|
|
|
|
month: (zeroBasedMonth % 12) + 1,
|
|
|
|
|
|
|
|
year: zeroBasedMonth ~/ 12,
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
final nextGregorianDate = calendarModel.hijriToGregorian(nextHijriDate);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setState(() {
|
|
|
|
|
|
|
|
_currentHijriDisplay = nextHijriDate;
|
|
|
|
|
|
|
|
_calendarController.displayDate = nextGregorianDate;
|
|
|
|
|
|
|
|
_displayedMonthKey = _monthKey(nextGregorianDate);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void _handleCalendarViewChanged(DateRangePickerViewChangedArgs args) {
|
|
|
|
|
|
|
|
final rangeStart = args.visibleDateRange.startDate;
|
|
|
|
|
|
|
|
final rangeEnd = args.visibleDateRange.endDate;
|
|
|
|
|
|
|
|
final displayedDate = _calendarController.displayDate ??
|
|
|
|
|
|
|
|
(rangeEnd == null
|
|
|
|
|
|
|
|
? rangeStart
|
|
|
|
|
|
|
|
: rangeStart?.add(Duration(days: rangeEnd.difference(rangeStart).inDays ~/ 2)));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (displayedDate == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final newMonthKey = _monthKey(displayedDate);
|
|
|
|
|
|
|
|
if (newMonthKey == _displayedMonthKey) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_displayedMonthKey = newMonthKey;
|
|
|
|
|
|
|
|
_currentHijriDisplay = null;
|
|
|
|
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
|
|
|
|
|
|
if (mounted) {
|
|
|
|
|
|
|
|
setState(() {});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Build unified calendar header for both Gregorian and Hijri
|
|
|
|
// Build unified calendar header for both Gregorian and Hijri
|
|
|
|
Widget _buildCalendarHeader(bool isArabic, DateRangCalenderModel calendarModel) {
|
|
|
|
Widget _buildCalendarHeader(bool isArabic, DateRangCalenderModel calendarModel) {
|
|
|
|
final displayedDate = _calendarController.displayDate ?? DateTime.now();
|
|
|
|
final displayedDate = _calendarController.displayDate ?? DateTime.now();
|
|
|
|
@ -609,12 +667,10 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
|
|
|
|
icon: Icon(Icons.chevron_left, color: AppColors.primaryRedColor),
|
|
|
|
icon: Icon(Icons.chevron_left, color: AppColors.primaryRedColor),
|
|
|
|
onPressed: () {
|
|
|
|
onPressed: () {
|
|
|
|
if (_viewMode == PickerViewMode.date) {
|
|
|
|
if (_viewMode == PickerViewMode.date) {
|
|
|
|
_calendarController.backward!();
|
|
|
|
|
|
|
|
// Update tracked Hijri display when navigating months
|
|
|
|
|
|
|
|
if (calendarModel.isHijri) {
|
|
|
|
if (calendarModel.isHijri) {
|
|
|
|
setState(() {
|
|
|
|
_changeHijriMonth(calendarModel, -1);
|
|
|
|
_currentHijriDisplay = null; // Will recalculate on next build
|
|
|
|
} else {
|
|
|
|
});
|
|
|
|
_calendarController.backward!();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (_viewMode == PickerViewMode.year) {
|
|
|
|
} else if (_viewMode == PickerViewMode.year) {
|
|
|
|
// Navigate years backward by 12
|
|
|
|
// Navigate years backward by 12
|
|
|
|
@ -625,7 +681,7 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
final newHijriDate = HijriGregDate(day: 1, month: hijriDate.month, year: hijriDate.year - 12);
|
|
|
|
final newHijriDate = HijriGregDate(day: 1, month: hijriDate.month, year: hijriDate.year - 12);
|
|
|
|
final newGregorianDate = calendarModel.hijriToGregorian(newHijriDate);
|
|
|
|
final newGregorianDate = calendarModel.hijriToGregorian(newHijriDate);
|
|
|
|
_calendarController.displayDate = DateTime(newGregorianDate.year, newGregorianDate.month, 1);
|
|
|
|
_calendarController.displayDate = newGregorianDate;
|
|
|
|
_currentHijriDisplay = newHijriDate;
|
|
|
|
_currentHijriDisplay = newHijriDate;
|
|
|
|
} catch (e) {
|
|
|
|
} catch (e) {
|
|
|
|
// Fallback
|
|
|
|
// Fallback
|
|
|
|
@ -645,12 +701,10 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
|
|
|
|
icon: Icon(Icons.chevron_right, color: AppColors.primaryRedColor),
|
|
|
|
icon: Icon(Icons.chevron_right, color: AppColors.primaryRedColor),
|
|
|
|
onPressed: () {
|
|
|
|
onPressed: () {
|
|
|
|
if (_viewMode == PickerViewMode.date) {
|
|
|
|
if (_viewMode == PickerViewMode.date) {
|
|
|
|
_calendarController.forward!();
|
|
|
|
|
|
|
|
// Update tracked Hijri display when navigating months
|
|
|
|
|
|
|
|
if (calendarModel.isHijri) {
|
|
|
|
if (calendarModel.isHijri) {
|
|
|
|
setState(() {
|
|
|
|
_changeHijriMonth(calendarModel, 1);
|
|
|
|
_currentHijriDisplay = null; // Will recalculate on next build
|
|
|
|
} else {
|
|
|
|
});
|
|
|
|
_calendarController.forward!();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (_viewMode == PickerViewMode.year) {
|
|
|
|
} else if (_viewMode == PickerViewMode.year) {
|
|
|
|
// Navigate years forward by 12
|
|
|
|
// Navigate years forward by 12
|
|
|
|
@ -661,7 +715,7 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
final newHijriDate = HijriGregDate(day: 1, month: hijriDate.month, year: hijriDate.year + 12);
|
|
|
|
final newHijriDate = HijriGregDate(day: 1, month: hijriDate.month, year: hijriDate.year + 12);
|
|
|
|
final newGregorianDate = calendarModel.hijriToGregorian(newHijriDate);
|
|
|
|
final newGregorianDate = calendarModel.hijriToGregorian(newHijriDate);
|
|
|
|
_calendarController.displayDate = DateTime(newGregorianDate.year, newGregorianDate.month, 1);
|
|
|
|
_calendarController.displayDate = newGregorianDate;
|
|
|
|
_currentHijriDisplay = newHijriDate;
|
|
|
|
_currentHijriDisplay = newHijriDate;
|
|
|
|
} catch (e) {
|
|
|
|
} catch (e) {
|
|
|
|
// Fallback
|
|
|
|
// Fallback
|
|
|
|
@ -696,7 +750,7 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
|
|
|
|
if (calendarModel.isHijri) {
|
|
|
|
if (calendarModel.isHijri) {
|
|
|
|
// For Hijri calendar
|
|
|
|
// For Hijri calendar
|
|
|
|
final hijriCurrent = calendarModel.gregorianToHijri(currentDate);
|
|
|
|
final hijriCurrent = calendarModel.gregorianToHijri(currentDate);
|
|
|
|
final hijriDisplayed = calendarModel.gregorianToHijri(displayedDate);
|
|
|
|
final hijriDisplayed = _currentHijriDisplay ?? calendarModel.gregorianToHijri(displayedDate);
|
|
|
|
currentMonth = hijriCurrent.month;
|
|
|
|
currentMonth = hijriCurrent.month;
|
|
|
|
currentYear = hijriCurrent.year;
|
|
|
|
currentYear = hijriCurrent.year;
|
|
|
|
displayedYear = hijriDisplayed.year;
|
|
|
|
displayedYear = hijriDisplayed.year;
|
|
|
|
@ -730,11 +784,9 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
final hijriDate = HijriGregDate(day: 1, month: monthIndex, year: displayedYear);
|
|
|
|
final hijriDate = HijriGregDate(day: 1, month: monthIndex, year: displayedYear);
|
|
|
|
final gregorianDate = calendarModel.hijriToGregorian(hijriDate);
|
|
|
|
final gregorianDate = calendarModel.hijriToGregorian(hijriDate);
|
|
|
|
_calendarController.displayDate = DateTime(gregorianDate.year, gregorianDate.month, 1);
|
|
|
|
_calendarController.displayDate = gregorianDate;
|
|
|
|
// Track the Hijri month/year for accurate header display
|
|
|
|
// Track the Hijri month/year for accurate header display
|
|
|
|
_currentHijriDisplay = hijriDate;
|
|
|
|
_currentHijriDisplay = hijriDate;
|
|
|
|
// Clear cache since we changed the month
|
|
|
|
|
|
|
|
_hijriCache.clear();
|
|
|
|
|
|
|
|
} catch (e) {
|
|
|
|
} catch (e) {
|
|
|
|
// Fallback if conversion fails
|
|
|
|
// Fallback if conversion fails
|
|
|
|
_calendarController.displayDate = DateTime(displayedDate.year, monthIndex, 1);
|
|
|
|
_calendarController.displayDate = DateTime(displayedDate.year, monthIndex, 1);
|
|
|
|
@ -784,7 +836,7 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
|
|
|
|
|
|
|
|
|
|
|
|
if (calendarModel.isHijri) {
|
|
|
|
if (calendarModel.isHijri) {
|
|
|
|
// For Hijri calendar
|
|
|
|
// For Hijri calendar
|
|
|
|
final hijriDisplayed = calendarModel.gregorianToHijri(displayedDate);
|
|
|
|
final hijriDisplayed = _currentHijriDisplay ?? calendarModel.gregorianToHijri(displayedDate);
|
|
|
|
final hijriCurrent = calendarModel.gregorianToHijri(DateTime.now());
|
|
|
|
final hijriCurrent = calendarModel.gregorianToHijri(DateTime.now());
|
|
|
|
displayedYear = hijriDisplayed.year;
|
|
|
|
displayedYear = hijriDisplayed.year;
|
|
|
|
currentDisplayYear = hijriCurrent.year;
|
|
|
|
currentDisplayYear = hijriCurrent.year;
|
|
|
|
@ -818,7 +870,7 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
|
|
|
|
final currentHijriMonth = _currentHijriDisplay?.month ?? calendarModel.gregorianToHijri(displayedDate).month;
|
|
|
|
final currentHijriMonth = _currentHijriDisplay?.month ?? calendarModel.gregorianToHijri(displayedDate).month;
|
|
|
|
final hijriDate = HijriGregDate(day: 1, month: currentHijriMonth, year: year);
|
|
|
|
final hijriDate = HijriGregDate(day: 1, month: currentHijriMonth, year: year);
|
|
|
|
final gregorianDate = calendarModel.hijriToGregorian(hijriDate);
|
|
|
|
final gregorianDate = calendarModel.hijriToGregorian(hijriDate);
|
|
|
|
_calendarController.displayDate = DateTime(gregorianDate.year, gregorianDate.month, 1);
|
|
|
|
_calendarController.displayDate = gregorianDate;
|
|
|
|
// Track the Hijri month/year for accurate header display
|
|
|
|
// Track the Hijri month/year for accurate header display
|
|
|
|
_currentHijriDisplay = hijriDate;
|
|
|
|
_currentHijriDisplay = hijriDate;
|
|
|
|
} catch (e) {
|
|
|
|
} catch (e) {
|
|
|
|
@ -876,34 +928,34 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
displayDate(String label, String? date, bool isNotSelected) => Expanded(
|
|
|
|
displayDate(String label, String? date, bool isNotSelected) => Expanded(
|
|
|
|
child: Row(
|
|
|
|
child: Row(
|
|
|
|
spacing: 12.h,
|
|
|
|
spacing: 12.h,
|
|
|
|
|
|
|
|
children: [
|
|
|
|
|
|
|
|
Utils.buildSvgWithAssets(icon: AppAssets.rangeCalendar, iconColor: isNotSelected ? AppColors.borderOnlyColor : AppColors.blackColor, height: 24, width: 24),
|
|
|
|
|
|
|
|
Column(
|
|
|
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
children: [
|
|
|
|
children: [
|
|
|
|
Utils.buildSvgWithAssets(icon: AppAssets.rangeCalendar, iconColor: isNotSelected ? AppColors.borderOnlyColor : AppColors.blackColor, height: 24, width: 24),
|
|
|
|
Text(
|
|
|
|
Column(
|
|
|
|
label,
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
style: TextStyle(
|
|
|
|
children: [
|
|
|
|
color: AppColors.inputLabelTextColor,
|
|
|
|
Text(
|
|
|
|
fontSize: 12.f,
|
|
|
|
label,
|
|
|
|
fontWeight: FontWeight.w600,
|
|
|
|
style: TextStyle(
|
|
|
|
),
|
|
|
|
color: AppColors.inputLabelTextColor,
|
|
|
|
),
|
|
|
|
fontSize: 12.f,
|
|
|
|
Text(
|
|
|
|
fontWeight: FontWeight.w600,
|
|
|
|
date!,
|
|
|
|
),
|
|
|
|
style: TextStyle(
|
|
|
|
),
|
|
|
|
color: AppColors.textColor,
|
|
|
|
Text(
|
|
|
|
fontSize: 14.f,
|
|
|
|
date!,
|
|
|
|
fontWeight: FontWeight.w600,
|
|
|
|
style: TextStyle(
|
|
|
|
),
|
|
|
|
color: AppColors.textColor,
|
|
|
|
|
|
|
|
fontSize: 14.f,
|
|
|
|
|
|
|
|
fontWeight: FontWeight.w600,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
)
|
|
|
|
)
|
|
|
|
],
|
|
|
|
],
|
|
|
|
),
|
|
|
|
)
|
|
|
|
);
|
|
|
|
],
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
selectionChip(DateRangeSelectorRangeViewModel model) {
|
|
|
|
selectionChip(DateRangeSelectorRangeViewModel model) {
|
|
|
|
return Row(
|
|
|
|
return Row(
|
|
|
|
|