Created
May 21, 2024 11:29
-
-
Save sagar2093/268115b8531867da9c567b67d8e0d362 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:collection/collection.dart'; | |
import 'package:flutter/material.dart'; | |
import 'package:intl/intl.dart'; | |
void main() { | |
runApp(const CalendarGeneratorApp()); | |
} | |
class CalendarGeneratorApp extends StatelessWidget { | |
const CalendarGeneratorApp({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
debugShowCheckedModeBanner: false, | |
themeMode: ThemeMode.light, | |
theme: ThemeData( | |
brightness: Brightness.light, | |
useMaterial3: true, | |
scaffoldBackgroundColor: Colors.grey.shade200, | |
colorSchemeSeed: Colors.blueAccent), | |
home: const YearlyCalendarScreen(), | |
); | |
} | |
} | |
class YearlyCalendarScreen extends StatefulWidget { | |
const YearlyCalendarScreen({super.key}); | |
@override | |
State<YearlyCalendarScreen> createState() => _YearlyCalendarScreenState(); | |
} | |
class _YearlyCalendarScreenState extends State<YearlyCalendarScreen> { | |
final DateTime _today = DateTime.now(); | |
final Map<int, List<DateTime>> _monthCellsMap = {}; | |
final List<String> _holidays = []; | |
final Map<String, String> _holidaysMap = {}; | |
@override | |
void initState() { | |
setHolidays(); | |
List.generate(12, (index) { | |
generateMonthCells(DateTime(_today.year, index + 1, 1)); | |
}); | |
super.initState(); | |
} | |
setHolidays() { | |
// US United States / Public holidays (2024) | |
_holidaysMap.addAll({ | |
"2024-01-01": "New Year's Day", | |
"2024-01-15": "Martin Luther King Jr. Day", | |
"2024-02-19": "Presidents' Day", | |
"2024-05-27": "Memorial Day", | |
"2024-06-19": "Juneteenth National Independence Day", | |
"2024-07-04": "Independence Day", | |
"2024-09-02": "Labor Day", | |
"2024-10-14": "Columbus Day", | |
"2024-11-11": "Veterans Day", | |
"2024-11-28": "Thanksgiving Day", | |
"2024-12-25": "Christmas Day", | |
}); | |
_holidays.addAll(_holidaysMap.keys.toList()); | |
} | |
generateMonthCells(DateTime month) async { | |
List<DateTime> cells = []; | |
var totalDaysInMonth = DateUtils.getDaysInMonth(month.year, month.month); | |
var firstDayDt = DateTime(month.year, month.month, 1); | |
var previousMonthDt = firstDayDt.subtract(const Duration(days: 1)); | |
var nextMonthDt = DateTime(month.year, month.month, totalDaysInMonth) | |
.add(const Duration(days: 1)); | |
var firstDayOfWeek = firstDayDt.weekday; | |
var previousMonthDays = | |
DateUtils.getDaysInMonth(previousMonthDt.year, previousMonthDt.month); | |
// previous month days | |
var previousMonthCells = List.generate( | |
firstDayOfWeek - 1, | |
(index) => DateTime(previousMonthDt.year, previousMonthDt.month, | |
previousMonthDays - index)); | |
cells.addAll(previousMonthCells.reversed); | |
// current month days | |
var currentMonthCells = List.generate(totalDaysInMonth, | |
(index) => DateTime(month.year, month.month, index + 1)); | |
cells.addAll(currentMonthCells); | |
// next month days | |
var remainingCellCount = 35 - cells.length; | |
if (cells.length > 35) { | |
remainingCellCount = 42 - cells.length; | |
} | |
var nextMonthCells = List.generate(remainingCellCount, | |
(index) => DateTime(nextMonthDt.year, nextMonthDt.month, index + 1)); | |
cells.addAll(nextMonthCells); | |
_monthCellsMap[month.month] = cells; | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar( | |
title: const Text("Custom Yearly Calendar"), | |
), | |
body: SafeArea( | |
child: SingleChildScrollView( | |
padding: const EdgeInsets.all(20), | |
child: Column( | |
children: [ | |
...List.generate(12, (index) { | |
var calendarMonth = DateTime(_today.year, index + 1, 1); | |
var cells = _monthCellsMap[index + 1] ?? []; | |
return Card( | |
color: Colors.white, | |
margin: const EdgeInsets.only(bottom: 20), | |
shape: RoundedRectangleBorder( | |
borderRadius: BorderRadius.circular(10)), | |
child: CalendarBlock( | |
calendarMonth, | |
cells, | |
_holidaysMap, | |
today: _today, | |
isJapanese: true, | |
header: Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: Text( | |
calendarMonth.calendarTitle, | |
style: Theme.of(context).textTheme.titleLarge, | |
), | |
), | |
), | |
); | |
}), | |
], | |
), | |
), | |
), | |
); | |
} | |
} | |
class CalendarBlock extends StatelessWidget { | |
const CalendarBlock( | |
this.month, | |
this.cells, | |
this.holidaysMap, { | |
required this.today, | |
this.header, | |
this.isJapanese = true, | |
super.key, | |
}); | |
final DateTime today; | |
final DateTime month; | |
final List<DateTime> cells; | |
final Map<String, String> holidaysMap; | |
final Widget? header; | |
final bool isJapanese; | |
@override | |
Widget build(BuildContext context) { | |
var holidays = holidaysMap.keys.toList(); | |
return Container( | |
padding: const EdgeInsets.all(4), | |
decoration: BoxDecoration( | |
color: Colors.white, borderRadius: BorderRadius.circular(14)), | |
child: Column( | |
children: [ | |
Align(alignment: Alignment.center, child: header ?? const SizedBox()), | |
const _WeekHeaders(), | |
GridView.builder( | |
shrinkWrap: true, | |
physics: const NeverScrollableScrollPhysics(), | |
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( | |
crossAxisCount: 7, | |
childAspectRatio: 0.9, | |
mainAxisSpacing: 2, | |
crossAxisSpacing: 2, | |
), | |
itemCount: cells.length, | |
itemBuilder: (context, index) { | |
var item = cells[index]; | |
var isToday = item.isToday; | |
var isSameMonth = item.month == month.month; | |
var isHoliday = holidays.contains(item.dateDash); | |
Color? bgColor; | |
Color? textColor; | |
if (item.isSaturday) { | |
textColor = Colors.blueAccent; | |
} | |
if (item.isSunday) { | |
textColor = Colors.red; | |
} | |
if (item.isToday && isHoliday) { | |
bgColor = Colors.red; | |
textColor = Colors.white; | |
} else if (isToday) { | |
bgColor = Colors.blueAccent; | |
textColor = Colors.white; | |
} else if (isHoliday) { | |
bgColor = const Color(0xFFFFEFEF); | |
textColor = Colors.red; | |
} | |
return Card( | |
color: bgColor, | |
shape: RoundedRectangleBorder( | |
borderRadius: BorderRadius.circular(10), | |
), | |
child: InkWell( | |
onTap: isHoliday | |
? () { | |
var holidayText = holidaysMap[item.dateDash] ?? | |
"This day is Holiday"; | |
showModalBottomSheet( | |
context: context, | |
backgroundColor: Colors.white, | |
builder: (_) { | |
return Padding( | |
padding: const EdgeInsets.all(20), | |
child: Column( | |
crossAxisAlignment: | |
CrossAxisAlignment.stretch, | |
mainAxisSize: MainAxisSize.min, | |
children: [ | |
Text( | |
"${item.dateDashEEEE} (Holiday)", | |
style: Theme.of(context) | |
.textTheme | |
.headlineSmall, | |
), | |
vs10x2, | |
Container( | |
padding: const EdgeInsets.all(20), | |
decoration: BoxDecoration( | |
color: const Color(0xFFffcc17) | |
.withOpacity(0.1), | |
borderRadius: | |
BorderRadius.circular(4)), | |
child: Text(holidayText), | |
), | |
vs10x2, | |
FilledButton( | |
onPressed: () { | |
Navigator.pop(context); | |
}, | |
child: const Text("Close"), | |
), | |
vs10x2, | |
], | |
)); | |
}); | |
} | |
: null, | |
borderRadius: BorderRadius.circular(10), | |
child: Opacity( | |
opacity: isSameMonth ? 1 : 0.3, | |
child: Stack( | |
children: [ | |
Center( | |
child: Text( | |
item.day.toString(), | |
style: TextStyle( | |
fontSize: 20, | |
fontWeight: FontWeight.w600, | |
color: textColor), | |
)), | |
], | |
), | |
), | |
), | |
); | |
}) | |
], | |
), | |
); | |
} | |
} | |
class _WeekHeaders extends StatelessWidget { | |
const _WeekHeaders({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
return SizedBox( | |
height: 28, | |
child: Row( | |
mainAxisAlignment: MainAxisAlignment.spaceEvenly, | |
children: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] | |
.mapIndexed( | |
(i, item) => Expanded( | |
child: Text( | |
item, | |
style: TextStyle( | |
fontSize: 14, | |
fontWeight: FontWeight.w700, | |
color: _getTitleColor(i), | |
), | |
textAlign: TextAlign.center, | |
), | |
), | |
) | |
.toList()), | |
); | |
} | |
_getTitleColor(int index) { | |
if (index == 5) { | |
return Colors.blueAccent; | |
} | |
if (index == 6) { | |
return const Color(0xFFFF8181); | |
} | |
return const Color(0xAD1A1642); | |
} | |
} | |
extension DateTimeExt on DateTime { | |
toFormat([String? newPattern, String? locale]) { | |
return DateFormat(newPattern, locale).format(this); | |
} | |
String get dateDash => toFormat("yyyy-MM-dd"); | |
String get dateDashTime => toFormat("yyyy-MM-dd HH:mm"); | |
String get dateDashEEEE => toFormat("yyyy-MM-dd EEEE"); | |
String get monthName => toFormat("MMMM"); | |
String get calendarTitle => toFormat("MMMM yyyy"); | |
bool get isSaturday => weekday == 6; | |
bool get isSunday => weekday == 7; | |
bool get isToday { | |
var today = DateTime.now(); | |
return year == today.year && month == today.month && day == today.day; | |
} | |
} | |
const vs10x2 = SizedBox(height: 20); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment