You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

414 lines
12 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import 'package:flutter/material.dart';
import '../../common/settings.dart';
class AdvancedDateRangePicker extends StatefulWidget {
final DateTimeRange? initialRange;
const AdvancedDateRangePicker({super.key, this.initialRange});
@override
State<AdvancedDateRangePicker> createState() => _AdvancedDateRangePickerState();
}
class _AdvancedDateRangePickerState extends State<AdvancedDateRangePicker> {
late DateTime _currentMonth;
DateTime? _start;
DateTime? _end;
late PageController _pageController;
int _initialPage = 500; // large buffer for infinite scroll
@override
void initState() {
super.initState();
_initialPage = 500;
_pageController = PageController(initialPage: _initialPage);
_currentMonth = DateTime(DateTime.now().year, DateTime.now().month, 1);
if (widget.initialRange != null) {
_start = widget.initialRange!.start;
_end = widget.initialRange!.end;
}
}
@override
Widget build(BuildContext context) {
return Dialog(
backgroundColor: Colors.white, // White background
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Container(
padding: const EdgeInsets.all(5),
width: 420,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildHeader(),
const SizedBox(height: 12),
_buildWeekDays(),
SizedBox(
height: 270,
child: PageView.builder(
controller: _pageController,
onPageChanged: (pageIndex) { //to update the current month state when user swipes
setState(() {
int diff = pageIndex - _initialPage;
_currentMonth = DateTime(
DateTime.now().year,
DateTime.now().month + diff,
1,
);
});
},
itemBuilder: (context, index) { //to build the calendar for each month
int diff = index - _initialPage;
DateTime monthToShow = DateTime(
DateTime.now().year,
DateTime.now().month + diff,
1,
);
return _buildCalendarGrid(monthToShow);
},
),
),
const SizedBox(height: 12),
_buildSelectionText(),
const SizedBox(height: 16),
_buildFooter()
],
),
),
);
}
// -----------------------------------------------------------
// 🔵 HEADER (Month Year + Arrows)
// -----------------------------------------------------------
Widget _buildHeader() {
return Column(
children: [
// Title
const Padding(
padding: EdgeInsets.symmetric(vertical: 8.0),
child: Text(
"Select a Date Range",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: const Icon(Icons.arrow_left, color: Colors.black),
onPressed: () {
_pageController.previousPage(
duration: const Duration(milliseconds: 250),
curve: Curves.easeOut,
);
},
),
GestureDetector(
onTap: () => _showYearPicker(context),
child: Text(
"${_monthName(_currentMonth.month)} ${_currentMonth.year}",
style: fontTextStyle(18, Colors.black, FontWeight.w400),
),
),
IconButton(
icon: const Icon(Icons.arrow_right, color: Colors.black),
onPressed: () {
_pageController.nextPage(
duration: const Duration(milliseconds: 250),
curve: Curves.easeOut,
);
},
),
],
)
],
);
}
// -----------------------------------------------------------
// 🔵 WEEK DAY ROW
// -----------------------------------------------------------
Widget _buildWeekDays() {
const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: days
.map((d) => Expanded(
child: Center(
child: Text(
d,
style: fontTextStyle(14, Colors.black, FontWeight.w600),
),
),
))
.toList(),
);
}
// -----------------------------------------------------------
// 🔵 BUILD CALENDAR GRID
// -----------------------------------------------------------
Widget _buildCalendarGrid(DateTime month) {
List<Widget> rows = [];
DateTime firstDay = DateTime(month.year, month.month, 1);
int startingWeekday = firstDay.weekday % 7; // Sunday=0
int daysInMonth = DateTime(month.year, month.month + 1, 0).day;
List<Widget> dayCells = [];
// Empty cells before month starts
for (int i = 0; i < startingWeekday; i++) {
dayCells.add(const Expanded(child: SizedBox()));
}
// Month days
for (int d = 1; d <= daysInMonth; d++) {
DateTime date = DateTime(month.year, month.month, d);
bool isToday = _isSame(date, DateTime.now()); // Highlight today
bool isSelectedStart = _isSame(date, _start);
bool isSelectedEnd = _isSame(date, _end);
bool inRange =
_start != null && _end != null && date.isAfter(_start!) && date.isBefore(_end!);
dayCells.add(
Expanded(
child: GestureDetector(
onTap: () => _onDateTap(date),
child: Container(
margin: const EdgeInsets.all(4),
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: isSelectedStart || isSelectedEnd
? Colors.black
: inRange
? Colors.black12
: Colors.transparent,
shape: BoxShape.circle,
border: isToday && !isSelectedStart && !isSelectedEnd
? Border.all(color: Colors.blueAccent, width: 2) // Highlight today
: null,
),
child: Center(
child: Text(
"$d",
style: TextStyle(
color: isSelectedStart || isSelectedEnd
? Colors.white
: Colors.black,
fontWeight: isToday ? FontWeight.bold : FontWeight.normal,
),
),
),
),
),
),
);
// Create a row after each week
if ((dayCells.length) % 7 == 0) {
rows.add(Row(children: dayCells));
dayCells = [];
}
}
// Last row fill remaining empty cells
if (dayCells.isNotEmpty) {
while (dayCells.length < 7) {
dayCells.add(const Expanded(child: SizedBox()));
}
rows.add(Row(children: dayCells));
}
return Column(children: rows);
}
// -----------------------------------------------------------
// 🔵 DATE TAP LOGIC
// -----------------------------------------------------------
void _onDateTap(DateTime date) {
setState(() {
if (_start == null || (_start != null && _end != null)) {
_start = date;
_end = null;
} else if (date.isBefore(_start!)) {
_end = _start;
_start = date;
} else {
_end = date;
}
});
}
// -----------------------------------------------------------
// 🔵 SELECTED TEXT
// -----------------------------------------------------------
// Widget _buildSelectionText() {
// return Text(
// _start == null
// ? "Choose Start Date"
// : _end == null
// ? "Choose End Date"
// : "Selected: ${_fmt(_start!)} → ${_fmt(_end!)}",
// style: fontTextStyle(14, Colors.blueAccent, FontWeight.w600),
// );
// }
Widget _buildSelectionText() {
if (_start == null || _end == null) {
return const SizedBox(); // Show nothing
}
return Text(
"Selected: ${_fmt(_start!)}${_fmt(_end!)}",
style: fontTextStyle(14, Colors.blueAccent, FontWeight.w600),
);
}
// -----------------------------------------------------------
// 🔵 FOOTER BUTTONS
// -----------------------------------------------------------
Widget _buildFooter() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
child: Text("Cancel", style: fontTextStyle(14, Colors.black, FontWeight.w600)),
onPressed: () => Navigator.pop(context),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.black,
),
onPressed: (_start != null && _end != null)
? () => Navigator.pop(
context,
DateTimeRange(start: _start!, end: _end!),
)
: null,
child: Text("Apply", style: fontTextStyle(14, Colors.white, FontWeight.bold)),
),
],
);
}
// -----------------------------------------------------------
// 🔧 HELPERS
// -----------------------------------------------------------
bool _isSame(DateTime? a, DateTime? b) {
if (a == null || b == null) return false;
return a.year == b.year && a.month == b.month && a.day == b.day;
}
String _fmt(DateTime d) => "${d.year}-${d.month}-${d.day}";
String _monthName(int m) {
const names = [
"January","February","March","April","May","June",
"July","August","September","October","November","December"
];
return names[m - 1];
}
// -----------------------------------------------------------
// 🔵 YEAR PICKER SHEET
// -----------------------------------------------------------
void _showYearPicker(BuildContext context) {
int currentYear = DateTime.now().year;
int startYear = currentYear - 25;
int endYear = currentYear + 25;
int selectedYear = _currentMonth.year;
ScrollController scrollController = ScrollController(
initialScrollOffset: (selectedYear - startYear) * 48.0, // 56 is approx ListTile height
);
showDialog(
context: context,
builder: (_) => Dialog(
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: SizedBox(
height: 300,
child: ListView.builder(
controller: scrollController,
itemCount: endYear - startYear + 1,
itemBuilder: (context, index) {
int y = startYear + index;
bool isSelected = y == selectedYear;
return Center(
child: GestureDetector(
onTap: () {
setState(() {
_currentMonth = DateTime(y, _currentMonth.month, 1);
});
Navigator.pop(context);
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 4),
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: BoxDecoration(
color: isSelected ? Colors.black : Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.black12),
),
child: Text(
"$y",
style: fontTextStyle(
16,
isSelected ? Colors.white : Colors.black,
FontWeight.bold,
),
),
),
),
);
},
),
),
),
);
}
//To clear the selected dates inside your custom calendar grid
void _clearCalendarSelection() {
setState(() {
_start = null;
_end = null;
});
}
}