first commit

This commit is contained in:
Hamza-Ayed
2026-06-09 08:40:31 +03:00
commit d8901e1a87
3161 changed files with 536187 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,568 @@
import '../controllers/month_builder_controller.dart';
import '../controllers/month_ui_controller.dart';
import 'package:get/get.dart';
import '../../calendar_builder.dart';
import 'package:flutter/material.dart';
//Todo: Add Image Documentation
///A year Drop down class--- default used in [CbMonthBuilder]
class CbYearDropDown extends StatefulWidget {
///
const CbYearDropDown({
Key? key,
this.matchId,
this.yearHeaderCustomizer,
this.yearHeaderBuilder,
this.yearButtonCustomizer,
this.yearButtonBuilder,
this.uiStateTag,
this.expandedYearHeight = 200,
this.expandedYearWidth,
}) : super(key: key);
/// if u want to use same data between your Calendar Builder -- make sure to give same [matchId]
final String? matchId;
///customise year Header style [YearHeaderCustomizer]
final YearHeaderCustomizer? yearHeaderCustomizer;
///customise year button style [YearButtonCustomizer]
final YearButtonCustomizer? yearButtonCustomizer;
///Build your custom YearDropDown Header
final WidgetCbYearHeader? yearHeaderBuilder;
///Build your own year buttons
final WidgetCbYearButton? yearButtonBuilder;
///Expanded Height of year dropDown
///default = 200
final double expandedYearHeight;
///Expanded Width of year dropDown
final double? expandedYearWidth;
///UI controller tag
final String? uiStateTag;
@override
_CbYearDropDownState createState() => _CbYearDropDownState();
}
class _CbYearDropDownState extends State<CbYearDropDown> {
late String matchingId;
late String uiStateTag;
@override
void initState() {
super.initState();
initControlers();
}
void initControlers() {
uiStateTag = widget.uiStateTag ?? UniqueKey().toString();
Get.put(MonthUiController(), tag: uiStateTag);
matchingId = widget.matchId ?? UniqueKey().toString();
Get.put(MonthBuilderController(), tag: matchingId);
}
@override
void dispose() {
super.dispose();
Get.delete<MonthUiController>(tag: uiStateTag);
Get.delete<MonthBuilderController>(tag: matchingId);
}
@override
Widget build(BuildContext context) {
String matchingId = widget.matchId ?? UniqueKey().toString();
final mUiCtr = Get.find<MonthUiController>(tag: uiStateTag);
final size = MediaQuery.of(context).size;
Widget _ifBuilderEmpty = SizedBox(
height: widget.yearHeaderCustomizer?.height ?? 40,
width: widget.yearHeaderCustomizer?.width ?? size.width,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
GetBuilder<MonthBuilderController>(
tag: matchingId,
id: 'dateChangeId',
builder: (mCtr) {
String preMonth = mCtr.mSelectedDate.year ==
mCtr.mSelectedYear.year
? '${CalendarGlobals.months[mCtr.mSelectedDate.month - 1]} - '
: '';
String year = '$preMonth${mCtr.mSelectedYear.year}';
return Text(
year,
style: widget.yearHeaderCustomizer?.titleTextStyle ??
const TextStyle(fontWeight: FontWeight.bold),
);
}),
GetBuilder<MonthUiController>(
tag: uiStateTag,
id: 'monthExpanded',
builder: (mCtr) => Icon(!mCtr.isYearPickerExpanded
? widget.yearHeaderCustomizer?.downIcon ??
Icons.arrow_drop_down_outlined
: widget.yearHeaderCustomizer?.upIcon ??
Icons.arrow_drop_up_outlined)),
],
),
);
return Column(
children: [
InkWell(
onTap: () {
mUiCtr.onYearHeaderExpanded?.call(!mUiCtr.isYearPickerExpanded);
mUiCtr.chageYearExpanded(
isExpanded: !mUiCtr.isYearPickerExpanded,
commonUpdateId: 'commonId',
updateId: 'monthExpanded');
},
child: widget.yearHeaderBuilder == null
? _ifBuilderEmpty
: GetBuilder<MonthUiController>(
id: 'monthExpanded',
tag: uiStateTag,
builder: (mUiCtr) => GetBuilder<MonthBuilderController>(
id: 'commonId',
tag: matchingId,
builder: (mCtr) {
String preMonth = mCtr.mSelectedDate.year ==
mCtr.mSelectedYear.year
? '${CalendarGlobals.months[mCtr.mSelectedDate.month - 1]} - '
: '';
String year = '$preMonth${mCtr.mSelectedYear.year}';
return widget.yearHeaderBuilder!(
mUiCtr.isYearPickerExpanded,
mCtr.mSelectedDate,
mCtr.mSelectedYear,
year,
);
}),
),
),
_YearDropDown(
matchId: matchingId,
uiStateTag: uiStateTag,
height: widget.expandedYearHeight,
width: widget.expandedYearWidth ?? size.width,
yearButtonBuilder: widget.yearButtonBuilder,
yearButtonCustomizer: widget.yearButtonCustomizer,
)
],
);
}
}
class _YearDropDown extends StatefulWidget {
const _YearDropDown({
Key? key,
required this.matchId,
required this.uiStateTag,
required this.height,
required this.width,
this.yearButtonBuilder,
this.yearButtonCustomizer,
}) : super(key: key);
final String matchId;
final String uiStateTag;
final double height;
final double width;
final WidgetCbYearButton? yearButtonBuilder;
final YearButtonCustomizer? yearButtonCustomizer;
@override
__YearDropDownState createState() => __YearDropDownState();
}
class __YearDropDownState extends State<_YearDropDown>
with SingleTickerProviderStateMixin {
@override
Widget build(BuildContext context) {
return GetBuilder<MonthUiController>(
tag: widget.uiStateTag,
id: 'monthExpanded',
builder: (mUiCtr) {
///!this scrolling will not work while we are using builder
// WidgetsBinding.instance!.addPostFrameCallback((_) {
// Scrollable.ensureVisible(
// mCtr.mYearPickerGlobalKey[8].currentContext!);
// });
return AnimatedSize(
curve: Curves.fastOutSlowIn,
duration: CalendarGlobals.kMinDuration,
reverseDuration: CalendarGlobals.kMinDuration,
child: AnimatedOpacity(
duration: CalendarGlobals.kMaxDuration,
opacity: mUiCtr.isYearPickerExpanded ? 1.0 : 0,
child: SizedBox(
height: mUiCtr.isYearPickerExpanded ? widget.height : 0,
width: widget.width,
child: Visibility(
///by using [Visibility] will improve the performace
visible: mUiCtr.isYearPickerExpanded,
child: GetBuilder<MonthBuilderController>(
id: 'monthExpanded',
tag: widget.matchId,
builder: (mCtr) => _YearBtnGridBuilder(
uiStateTag: widget.uiStateTag,
matchId: widget.matchId,
height: widget.height,
width: widget.width,
mCtr: mCtr,
isExpanded: mUiCtr.isYearPickerExpanded,
yearButtonBuilder: widget.yearButtonBuilder,
yearButtonCustomizer: widget.yearButtonCustomizer,
),
),
),
),
),
);
},
);
}
}
class _YearBtnGridBuilder extends StatefulWidget {
const _YearBtnGridBuilder({
Key? key,
required this.height,
required this.width,
required this.mCtr,
required this.isExpanded,
required this.matchId,
required this.uiStateTag,
this.yearButtonBuilder,
this.yearButtonCustomizer,
}) : super(key: key);
final double height;
final double width;
final MonthBuilderController mCtr;
final bool isExpanded;
final String matchId;
final String uiStateTag;
final WidgetCbYearButton? yearButtonBuilder;
final YearButtonCustomizer? yearButtonCustomizer;
@override
__YearBtnGridBuilderState createState() => __YearBtnGridBuilderState();
}
class __YearBtnGridBuilderState extends State<_YearBtnGridBuilder>
with AutomaticKeepAliveClientMixin {
late ScrollController _controller;
late double scrollPosition;
///[scrollPosition] it finds the exact scroll position of the [cbYearDropDown]
///
///
///[.truncate] is used to remove fractional part/decimal part
///``if val = 1.89
///val.truncate()
///it returns 1 and removes fractoinal part``
void _findScrollPositon() {
scrollPosition = (((((widget.mCtr.m16DateTimeYears.isEmpty
? widget.mCtr.mAllYears.indexWhere((element) =>
element.year == widget.mCtr.mSelectedYear.year)
: widget.mCtr.m16DateTimeYears.indexWhere((element) =>
element.year == widget.mCtr.mSelectedYear.year))) /
4)
.truncate() *
(widget.height / 4)) -
(widget.height / 4));
}
@override
void initState() {
super.initState();
_findScrollPositon();
_controller = ScrollController(
initialScrollOffset: scrollPosition, keepScrollOffset: true);
}
@override
void didUpdateWidget(covariant _YearBtnGridBuilder oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.isExpanded != widget.isExpanded) {
_findScrollPositon();
_controller.jumpTo(
scrollPosition,
);
}
}
@override
void dispose() {
super.dispose();
_controller.dispose();
}
@override
Widget build(BuildContext context) {
super.build(context);
final bool isMaxSize = widget.width >= 1200;
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
///height of 1 button in grid
mainAxisExtent: isMaxSize ? widget.height / 3 : widget.height / 4,
crossAxisCount: isMaxSize ? 5 : 4,
),
//!same
// SliverGridDelegateWithMaxCrossAxisExtent(
// maxCrossAxisExtent:widget.width / 4,
// mainAxisExtent: widget.height / 4,
// ),
controller: _controller,
addAutomaticKeepAlives: true,
cacheExtent: 20,
physics: const BouncingScrollPhysics(),
itemCount: widget.mCtr.m16DateTimeYears.isEmpty
? widget.mCtr.mAllYears.length
: (widget.mCtr.m16DateTimeYears.length - (isMaxSize ? 1 : 0)),
itemBuilder: (context, i) => GetBuilder<MonthBuilderController>(
tag: widget.matchId,
id: widget.mCtr.m16DateTimeYears.isEmpty
? widget.mCtr.mAllYears[i]
: widget.mCtr.m16DateTimeYears[i],
builder: (mCtr) {
// Global.debugLogs('Year:$i');
DateTime thisLoopDate = mCtr.m16DateTimeYears.isEmpty
? mCtr.mAllYears[i]
: mCtr.m16DateTimeYears[i];
bool isYearselected = DateUtilsCB.checkYearIsSelected(
///
isDisabled: DateUtilsCB.checkDayisDisabled(
listOfDates: mCtr.mAllYears, date: thisLoopDate),
///
dateSelected: mCtr.mSelectedYear,
loopedDay: thisLoopDate,
);
bool isyearDisabled = DateUtilsCB.checkYearisDisabled(
listOfDates: mCtr.mAllYears,
date: thisLoopDate,
);
return FittedBox(
fit: BoxFit.scaleDown,
child: _YearButtons(
matchId: widget.matchId,
uiStateTag: widget.uiStateTag,
isyearDisabled: isyearDisabled,
thisLoopDate: thisLoopDate,
isYearselected: isYearselected,
height: widget.height,
width: widget.width,
builder: widget.yearButtonBuilder,
borderColorOnDisabled:
widget.yearButtonCustomizer?.borderColorOnDisabled,
borderColorOnEnabled:
widget.yearButtonCustomizer?.borderColorOnEnabled,
borderColorOnSelected:
widget.yearButtonCustomizer?.borderColorOnSelected,
shrinkOnButtonPressed: widget.yearButtonCustomizer == null
? true
: widget.yearButtonCustomizer!.shrinkOnButtonPressed,
hoverColor: widget.yearButtonCustomizer?.hoverColor,
onPressed: widget.yearButtonCustomizer?.onPressed,
splashColor: widget.yearButtonCustomizer?.splashColor,
textColorOnDisabled:
widget.yearButtonCustomizer?.textColorOnDisabled,
textColorOnEnabled:
widget.yearButtonCustomizer?.textColorOnEnabled,
textColorOnSelected:
widget.yearButtonCustomizer?.textColorOnSelected,
),
);
},
),
);
}
@override
bool get wantKeepAlive => true;
}
class _YearButtons extends StatefulWidget {
const _YearButtons({
Key? key,
required this.isyearDisabled,
required this.thisLoopDate,
required this.isYearselected,
required this.height,
required this.width,
required this.matchId,
required this.uiStateTag,
this.builder,
this.borderColorOnSelected,
this.borderColorOnDisabled,
this.borderColorOnEnabled,
this.textColorOnSelected,
this.textColorOnDisabled,
this.textColorOnEnabled,
this.splashColor,
this.hoverColor,
this.onPressed,
this.shrinkOnButtonPressed = true,
}) : super(key: key);
final bool isyearDisabled;
final DateTime thisLoopDate;
final bool isYearselected;
final double height;
final double width;
final String matchId;
final String uiStateTag;
final WidgetCbYearButton? builder;
///
final Color? borderColorOnSelected;
final Color? borderColorOnDisabled;
final Color? borderColorOnEnabled;
final Color? textColorOnSelected;
final Color? textColorOnDisabled;
final Color? textColorOnEnabled;
final Color? splashColor;
final Color? hoverColor;
final Function(bool isButtonDisabled, DateTime selectedYear)? onPressed;
final bool shrinkOnButtonPressed;
@override
__YearButtons createState() => __YearButtons();
}
class __YearButtons extends State<_YearButtons>
with AutomaticKeepAliveClientMixin {
///Callback when a year button is pressed
void _onYearPressed() {
final mCtr = Get.find<MonthBuilderController>(tag: widget.matchId);
final mUiCtr = Get.find<MonthUiController>(tag: widget.uiStateTag);
if (widget.isyearDisabled) {
if (widget.onPressed != null) {
widget.onPressed!(widget.isyearDisabled, widget.thisLoopDate);
}
CalendarGlobals.debugLogs('Calendar_builder: year button disabled');
} else {
if (widget.onPressed != null) {
widget.onPressed!(widget.isyearDisabled, widget.thisLoopDate);
}
///Changes selected date
mCtr.changeSelectedYear(
selectedYear: widget.thisLoopDate,
oldSelectedYear: DateTime(mCtr.mSelectedYear.year,
mCtr.mSelectedYear.month, mCtr.mSelectedYear.day),
updateByID: true,
updateId: 'dateChangeId',
commonUpdateId: 'commonId');
///isExpanded
if (widget.shrinkOnButtonPressed) {
Future.delayed(const Duration(milliseconds: 200)).then((value) {
mUiCtr.chageYearExpanded(
isExpanded: false,
updateId: 'monthExpanded',
commonUpdateId: 'commonId');
});
}
///changes the page of month pageView
int _mPage = mCtr.mAllYears
.indexWhere((element) => element.year == widget.thisLoopDate.year);
mUiCtr.mPageController.jumpToPage(_mPage);
///saves and removes months if length exedes 3
mCtr.addToSavedMonthDates(DateTime(mCtr.mSelectedYear.year,
mCtr.mSelectedYear.month, mCtr.mSelectedYear.day));
mCtr.savedMonthRemover(DateTime(widget.thisLoopDate.year,
widget.thisLoopDate.month, widget.thisLoopDate.day));
}
mUiCtr.onYearButtonClicked
?.call(widget.thisLoopDate, widget.isyearDisabled ? false : true);
}
@override
Widget build(BuildContext context) {
super.build(context);
final txtTheme = Theme.of(context).textTheme;
final colorTheme = Theme.of(context);
Widget ifBuilderisEmpty = SizedBox(
height: (widget.height / 5.5) >= 35 ? 35 : widget.height / 5.5,
width: (widget.width / 6) >= 150 ? 150 : (widget.width / 6),
child: InkWell(
onTap: _onYearPressed,
splashColor:
widget.isyearDisabled ? Colors.transparent : widget.splashColor,
hoverColor: widget.isyearDisabled ? null : widget.hoverColor,
customBorder: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(
(widget.height / 5.5) >= 35 ? 35 : widget.height / 5.5),
),
),
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(
(widget.height / 5.5) >= 35 ? 35 : widget.height / 5.5),
),
border: Border.all(
color: widget.isyearDisabled
? widget.borderColorOnDisabled ??
colorTheme.disabledColor.withOpacity(0.1)
: widget.isYearselected
? widget.borderColorOnSelected ??
(colorTheme.brightness == Brightness.dark
? colorTheme.colorScheme.secondary
: Colors.black)
: widget.borderColorOnEnabled ?? Colors.grey,
width: widget.isyearDisabled
? 1
: widget.isYearselected
? 1.5
: 1),
),
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(widget.thisLoopDate.year.toString(),
style: widget.isyearDisabled
? txtTheme.titleMedium!
.copyWith(color: widget.textColorOnDisabled)
: widget.isYearselected
? txtTheme.bodyLarge!
.copyWith(color: widget.textColorOnSelected)
: txtTheme.bodyLarge!
.copyWith(color: widget.textColorOnEnabled)),
),
),
),
);
return widget.builder == null
? ifBuilderisEmpty
: GestureDetector(
onTap: () {
///execute functions
_onYearPressed();
},
behavior: HitTestBehavior.opaque,
child: widget.builder!(
widget.thisLoopDate,
(widget.height / 5.5) >= 35 ? 35 : widget.height / 5.5,
(widget.width / 6) >= 150 ? 150 : (widget.width / 6),
widget.isyearDisabled,
widget.isYearselected,
));
}
@override
bool get wantKeepAlive => true;
}