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

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;
});
}
}