diff --git a/example/lib/main.dart b/example/lib/main.dart index d64e903..b2621bf 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -27,23 +27,36 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State { DatePickerController _controller = DatePickerController(); - DateTime _selectedValue = DateTime.now(); - + DateTime _selectedDayValue = DateTime.now(); + DateTime _selectedMonthValue = DateTime.now(); @override void initState() { super.initState(); } + // function to get total days in a month + int daysInMonth(DateTime date) { + var firstDayThisMonth = firstDayOfMonth(date); + var firstDayNextMonth = new DateTime(date.year, date.month + 1, 1); + + return firstDayNextMonth.difference(firstDayThisMonth).inDays; + } + + // function to get the first day of the month + DateTime firstDayOfMonth(DateTime date) { + return DateTime(date.year, date.month, 1); + } + @override Widget build(BuildContext context) { return Scaffold( - floatingActionButton: FloatingActionButton( - child: Icon(Icons.replay), - onPressed: () { - _controller.animateToSelection(); - }, - ), + floatingActionButton: FloatingActionButton( + child: Icon(Icons.replay), + onPressed: () { + _controller.animateToSelection(); + }, + ), appBar: AppBar( title: Text(widget.title!), ), @@ -53,23 +66,47 @@ class _MyHomePageState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text("You Selected:"), + Text("You Selected Month:"), Padding( padding: EdgeInsets.all(10), ), - Text(_selectedValue.toString()), + Text(_selectedMonthValue.toString()), + Padding( + padding: EdgeInsets.all(20), + ), + Text("You Selected Day:"), + Padding( + padding: EdgeInsets.all(10), + ), + Text(_selectedDayValue.toString()), Padding( padding: EdgeInsets.all(20), ), + Container( + child: MonthPicker( + startDate: DateTime.now(), + height: 50, + initialSelectedDate: _selectedMonthValue, + selectionColor: Colors.black, + selectedTextColor: Colors.white, + onDateChange: (date) { + // New date selected + setState(() { + _selectedMonthValue = date; + }); + }, + ), + ), Container( child: DatePicker( - DateTime.now(), + firstDayOfMonth(_selectedMonthValue), width: 60, height: 80, controller: _controller, - initialSelectedDate: DateTime.now(), + initialSelectedDate: _selectedDayValue, selectionColor: Colors.black, selectedTextColor: Colors.white, + daysCount: daysInMonth(_selectedMonthValue), inactiveDates: [ DateTime.now().add(Duration(days: 3)), DateTime.now().add(Duration(days: 4)), @@ -78,11 +115,26 @@ class _MyHomePageState extends State { onDateChange: (date) { // New date selected setState(() { - _selectedValue = date; + _selectedDayValue = date; }); }, ), ), + Container( + child: YearPickerTimeline( + startDate: DateTime.now(), + height: 45, + initialSelectedDate: _selectedMonthValue, + selectionColor: Colors.black, + selectedTextColor: Colors.white, + onDateChange: (date) { + // New date selected + setState(() { + _selectedMonthValue = date; + }); + }, + ), + ) ], ), )); diff --git a/example/pubspec.lock b/example/pubspec.lock index c2c3800..8cfebe8 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" boolean_selector: dependency: transitive description: @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: characters - sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" clock: dependency: transitive description: @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.17.2" cupertino_icons: dependency: "direct main" description: @@ -78,50 +78,42 @@ packages: dependency: transitive description: name: intl - sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" - url: "https://pub.dev" - source: hosted - version: "0.18.1" - js: - dependency: transitive - description: - name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.19.0" matcher: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.1" path: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.8.3" sky_engine: dependency: transitive description: flutter @@ -131,10 +123,10 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: @@ -171,10 +163,10 @@ packages: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.6.0" vector_math: dependency: transitive description: @@ -183,5 +175,13 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" sdks: - dart: ">=2.18.0 <3.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" diff --git a/lib/date_picker_timeline.dart b/lib/date_picker_timeline.dart index dea4fb8..ae5a25b 100644 --- a/lib/date_picker_timeline.dart +++ b/lib/date_picker_timeline.dart @@ -1,3 +1,5 @@ library date_picker_timeline; export 'date_picker_widget.dart'; +export 'month_picker_widget.dart'; +export 'year_picker_widget.dart'; diff --git a/lib/extra/color.dart b/lib/extra/color.dart index 2049190..affdca6 100644 --- a/lib/extra/color.dart +++ b/lib/extra/color.dart @@ -9,9 +9,12 @@ import 'package:flutter/material.dart'; class AppColors { AppColors._(); + static const Color defaultBackgroundColor = Color(0xFF434343); static const Color defaultDateColor = Colors.black; static const Color defaultDayColor = Colors.black; static const Color defaultMonthColor = Colors.black; + static const Color defaultYearColor = Colors.black; static const Color defaultSelectionColor = Color(0x30000000); static const Color defaultDeactivatedColor = Color(0xFF666666); + static const Color defaultSelectedMonthColor = Colors.white; } diff --git a/lib/extra/dimen.dart b/lib/extra/dimen.dart index a5d3179..bb11a17 100644 --- a/lib/extra/dimen.dart +++ b/lib/extra/dimen.dart @@ -11,4 +11,6 @@ class Dimen { static const double dateTextSize = 24; static const double dayTextSize = 11; static const double monthTextSize = 11; + static const double yearTextSize = 18; + static const double selectedMonthTextSize = 13; } diff --git a/lib/extra/extensions.dart b/lib/extra/extensions.dart new file mode 100644 index 0000000..64bcf49 --- /dev/null +++ b/lib/extra/extensions.dart @@ -0,0 +1,3 @@ +extension E on String { + String lastChars(int n) => substring(length - n); +} diff --git a/lib/extra/style.dart b/lib/extra/style.dart index a72aa53..8f30137 100644 --- a/lib/extra/style.dart +++ b/lib/extra/style.dart @@ -1,6 +1,12 @@ import 'package:date_picker_timeline/extra/color.dart'; -import 'package:flutter/material.dart'; import 'package:date_picker_timeline/extra/dimen.dart'; +import 'package:flutter/material.dart'; + +const TextStyle defaultYearTextStyle = TextStyle( + color: AppColors.defaultYearColor, + fontSize: Dimen.yearTextSize, + fontWeight: FontWeight.w500, +); const TextStyle defaultMonthTextStyle = TextStyle( color: AppColors.defaultMonthColor, @@ -19,3 +25,9 @@ const TextStyle defaultDayTextStyle = TextStyle( fontSize: Dimen.dayTextSize, fontWeight: FontWeight.w500, ); + +final TextStyle defaultSelectedMonthTextStyle = const TextStyle( + color: AppColors.defaultSelectedMonthColor, + fontSize: Dimen.selectedMonthTextSize, + fontWeight: FontWeight.bold, +); diff --git a/lib/month_picker_widget.dart b/lib/month_picker_widget.dart new file mode 100644 index 0000000..5a85680 --- /dev/null +++ b/lib/month_picker_widget.dart @@ -0,0 +1,186 @@ +import 'package:date_picker_timeline/extra/color.dart'; +import 'package:date_picker_timeline/extra/style.dart'; +import 'package:date_picker_timeline/month_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/date_symbol_data_local.dart'; + +import 'gestures/tap.dart'; + +class MonthPicker extends StatefulWidget { + /// Height of the selector + final double height; + final double width; + final DateTime startDate; + final DateTime? initialSelectedDate; + + final int monthCount; + final String locale; + final TextStyle? monthTextStyle, dateTextStyle; + final MonthPickerTimelineController? controller; + final DateChangeListener? onDateChange; + + final Color selectedTextColor; + final Color selectionColor; + final Color iconColor; + final bool showIcon; + + const MonthPicker( + {Key? key, + required this.startDate, + this.initialSelectedDate, + this.width = 60, + this.monthCount = 12, + this.height = 80, + this.locale = "pt_BR", + this.monthTextStyle = defaultMonthTextStyle, + this.dateTextStyle = defaultDateTextStyle, + this.controller, + this.onDateChange, + this.selectedTextColor = Colors.white, + this.iconColor = Colors.white, + this.selectionColor = AppColors.defaultSelectionColor, + this.showIcon = false}) + : super(key: key); + + @override + State createState() => _MonthPickerState(); +} + +class _MonthPickerState extends State { + DateTime? _currentDate; + + final ScrollController _controller = ScrollController(); + + final monthTimelineController = MonthPickerTimelineController(); + + late final TextStyle selectedDateStyle; + late final TextStyle selectedMonthStyle; + + @override + void initState() { + initializeDateFormatting(widget.locale, null); + + _currentDate = widget.initialSelectedDate; + + selectedDateStyle = widget.dateTextStyle!.copyWith(color: widget.selectedTextColor); + selectedMonthStyle = widget.monthTextStyle!.copyWith(color: widget.selectedTextColor, fontWeight: FontWeight.bold); + + if (widget.controller != null) { + widget.controller!.setMonthTimelineState(this); + } + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + height: widget.height, + width: widget.width * widget.monthCount, + child: ListView.builder( + itemCount: widget.monthCount, + scrollDirection: Axis.horizontal, + controller: _controller, + reverse: true, + itemBuilder: (context, index) { + DateTime date = widget.startDate.subtract(Duration(days: index * 30)); + date = _firstDayOfMonth(date); + + // Check if this date is the one that is currently selected + bool isSelected = _currentDate != null ? DateUtils.isSameMonth(date, _currentDate!) : false; + + return MonthWidget( + width: widget.width, + locale: widget.locale, + showIcon: widget.showIcon, + month: date, + isSelected: isSelected, + monthTextStyle: isSelected ? selectedMonthStyle : widget.monthTextStyle, + dateTextStyle: isSelected ? selectedDateStyle : widget.dateTextStyle, + selectionColor: isSelected ? widget.selectionColor : Colors.transparent, + iconColor: isSelected ? widget.iconColor : widget.selectionColor, + onDateSelected: (selectedDate) { + // A date is selected + if (widget.onDateChange != null) { + widget.onDateChange!(selectedDate); + } + setState(() { + _currentDate = selectedDate; + }); + }, + ); + }, + ), + ); + } + + // function to convert month to last day of month + DateTime _lastDayOfMonth(DateTime date) { + return DateTime(date.year, date.month + 1, 0); + } + + // function to convert month to fist day of month + DateTime _firstDayOfMonth(DateTime date) { + return DateTime(date.year, date.month, 1); + } + + bool _compareDate(DateTime date1, DateTime date2) { + return date1.day == date2.day && date1.month == date2.month && date1.year == date2.year; + } +} + +class MonthPickerTimelineController { + _MonthPickerState? _monthTimelineState; + + void setMonthTimelineState(_MonthPickerState state) { + _monthTimelineState = state; + } + + void jumpToSelection() { + assert(_monthTimelineState != null, 'DatePickerController is not attached to any DatePicker View.'); + + // jump to the current Date + _monthTimelineState!._controller.jumpTo(_calculateDateOffset(_monthTimelineState!._currentDate!)); + } + + /// This function will animate the Timeline to the currently selected Date + void animateToSelection({duration = const Duration(milliseconds: 500), curve = Curves.linear}) { + assert(_monthTimelineState != null, 'DatePickerController is not attached to any DatePicker View.'); + + // animate to the current date + _monthTimelineState!._controller + .animateTo(_calculateDateOffset(_monthTimelineState!._currentDate!), duration: duration, curve: curve); + } + + /// This function will animate to any date that is passed as an argument + /// In case a date is out of range nothing will happen + void animateToDate(DateTime date, {duration = const Duration(milliseconds: 500), curve = Curves.linear}) { + assert(_monthTimelineState != null, 'MonthTimelineController is not attached to any DatePicker View.'); + + _monthTimelineState!._controller.animateTo(_calculateDateOffset(date), duration: duration, curve: curve); + } + + /// This function will animate to any date that is passed as an argument + /// this will also set that date as the current selected date + void setDateAndAnimate(DateTime date, {duration = const Duration(milliseconds: 500), curve = Curves.linear}) { + assert(_monthTimelineState != null, 'DatePickerController is not attached to any DatePicker View.'); + + _monthTimelineState!._controller.animateTo(_calculateDateOffset(date), duration: duration, curve: curve); + + if (date.compareTo(_monthTimelineState!.widget.startDate) >= 0 && + date.compareTo(_monthTimelineState!.widget.startDate.add(Duration(days: _monthTimelineState!.widget.monthCount))) <= 0) { + // date is in the range + _monthTimelineState!._currentDate = date; + } + } + + /// Calculate the number of pixels that needs to be scrolled to go to the + /// date provided in the argument + double _calculateDateOffset(DateTime date) { + final startDate = DateTime(_monthTimelineState!.widget.startDate.year, _monthTimelineState!.widget.startDate.month, + _monthTimelineState!.widget.startDate.day); + + int offset = date.difference(startDate).inDays; + return (offset * _monthTimelineState!.widget.width) + (offset * 6); + } +} diff --git a/lib/month_widget.dart b/lib/month_widget.dart new file mode 100644 index 0000000..8661169 --- /dev/null +++ b/lib/month_widget.dart @@ -0,0 +1,72 @@ +import 'package:date_picker_timeline/extra/extensions.dart'; +import 'package:date_picker_timeline/gestures/tap.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +class MonthWidget extends StatelessWidget { + final double? width; + final DateTime month; + final TextStyle? monthTextStyle, dateTextStyle; + final Color selectionColor, iconColor; + final DateSelectionCallback? onDateSelected; + final String? locale; + final bool isSelected; + final bool showIcon; + + const MonthWidget({ + required this.month, + required this.monthTextStyle, + required this.dateTextStyle, + required this.selectionColor, + required this.isSelected, + required this.iconColor, + this.width, + this.onDateSelected, + this.locale, + this.showIcon = false, + }); + + @override + Widget build(BuildContext context) { + return InkWell( + child: Container( + width: width, + margin: const EdgeInsets.all(3.0), + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + color: selectionColor, + ), + child: Padding( + padding: const EdgeInsets.all(8), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (showIcon) + Padding( + padding: const EdgeInsets.only(top: 2.0), + child: Icon( + Icons.circle, + color: iconColor, + size: isSelected ? 12 : 8, + ), + ), + Text( + "${DateFormat("MMM", locale).format(month).replaceAll(RegExp(r'[.]+'), '').toUpperCase()}\n${DateFormat("y", locale).format(month).toUpperCase().lastChars(2)}", // Month + style: monthTextStyle, + textAlign: TextAlign.center, + ), + ], + ), + ), + ), + onTap: () { + // Check if onDateSelected is not null + if (onDateSelected != null) { + // Call the onDateSelected Function + onDateSelected?.call(month); + } + }, + ); + } +} diff --git a/lib/year_picker_widget.dart b/lib/year_picker_widget.dart new file mode 100644 index 0000000..d412a7c --- /dev/null +++ b/lib/year_picker_widget.dart @@ -0,0 +1,182 @@ +import 'package:date_picker_timeline/extra/color.dart'; +import 'package:date_picker_timeline/extra/style.dart'; +import 'package:date_picker_timeline/year_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/date_symbol_data_local.dart'; + +import 'gestures/tap.dart'; + +class YearPickerTimeline extends StatefulWidget { + /// Height of the selector + final double height; + final double width; + final DateTime startDate; + final DateTime? initialSelectedDate; + + final int yearCount; + final String locale; + final TextStyle? yearTextStyle; + final YearPickerTimelineController? controller; + final DateChangeListener? onDateChange; + + final Color selectedTextColor; + final Color selectionColor; + final Color iconColor; + final bool showIcon; + + const YearPickerTimeline({ + Key? key, + required this.startDate, + this.initialSelectedDate, + this.width = 60, + this.yearCount = 12, + this.height = 80, + this.locale = "pt_BR", + this.yearTextStyle = defaultYearTextStyle, + this.controller, + this.onDateChange, + this.selectedTextColor = Colors.white, + this.iconColor = Colors.white, + this.selectionColor = AppColors.defaultSelectionColor, + this.showIcon = false, + }) : super(key: key); + + @override + State createState() => _YearPickerTimelineState(); +} + +class _YearPickerTimelineState extends State { + DateTime? _currentDate; + + final ScrollController _controller = ScrollController(); + + final yearTimelineController = YearPickerTimelineController(); + + late final TextStyle selectedYearStyle; + + @override + void initState() { + initializeDateFormatting(widget.locale, null); + + _currentDate = widget.initialSelectedDate; + + selectedYearStyle = widget.yearTextStyle!.copyWith(color: widget.selectedTextColor, fontWeight: FontWeight.bold); + + if (widget.controller != null) { + widget.controller!.setMonthTimelineState(this); + } + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + height: widget.height, + width: widget.width * widget.yearCount, + child: ListView.builder( + itemCount: widget.yearCount, + scrollDirection: Axis.horizontal, + controller: _controller, + reverse: true, + itemBuilder: (context, index) { + DateTime date = widget.startDate.subtract(Duration(days: index * 365)); + date = _firstDayOfMonth(date); + + // Check if this date is the one that is currently selected + bool isSelected = _currentDate != null ? DateUtils.isSameMonth(date, _currentDate!) : false; + + return YearWidget( + width: widget.width, + locale: widget.locale, + showIcon: widget.showIcon, + month: date, + isSelected: isSelected, + yearTextStyle: isSelected ? selectedYearStyle : widget.yearTextStyle, + selectionColor: isSelected ? widget.selectionColor : Colors.transparent, + iconColor: isSelected ? widget.iconColor : widget.selectionColor, + onDateSelected: (selectedDate) { + // A date is selected + if (widget.onDateChange != null) { + widget.onDateChange!(selectedDate); + } + setState(() { + _currentDate = selectedDate; + }); + }, + ); + }, + ), + ); + } + + // function to convert month to last day of month + DateTime _lastDayOfMonth(DateTime date) { + return DateTime(date.year, date.month + 1, 0); + } + + // function to convert month to fist day of month + DateTime _firstDayOfMonth(DateTime date) { + return DateTime(date.year, date.month, 1); + } + + bool _compareDate(DateTime date1, DateTime date2) { + return date1.day == date2.day && date1.month == date2.month && date1.year == date2.year; + } +} + +class YearPickerTimelineController { + _YearPickerTimelineState? _monthTimelineState; + + void setMonthTimelineState(_YearPickerTimelineState state) { + _monthTimelineState = state; + } + + void jumpToSelection() { + assert(_monthTimelineState != null, 'DatePickerController is not attached to any DatePicker View.'); + + // jump to the current Date + _monthTimelineState!._controller.jumpTo(_calculateDateOffset(_monthTimelineState!._currentDate!)); + } + + /// This function will animate the Timeline to the currently selected Date + void animateToSelection({duration = const Duration(milliseconds: 500), curve = Curves.linear}) { + assert(_monthTimelineState != null, 'DatePickerController is not attached to any DatePicker View.'); + + // animate to the current date + _monthTimelineState!._controller + .animateTo(_calculateDateOffset(_monthTimelineState!._currentDate!), duration: duration, curve: curve); + } + + /// This function will animate to any date that is passed as an argument + /// In case a date is out of range nothing will happen + void animateToDate(DateTime date, {duration = const Duration(milliseconds: 500), curve = Curves.linear}) { + assert(_monthTimelineState != null, 'MonthTimelineController is not attached to any DatePicker View.'); + + _monthTimelineState!._controller.animateTo(_calculateDateOffset(date), duration: duration, curve: curve); + } + + /// This function will animate to any date that is passed as an argument + /// this will also set that date as the current selected date + void setDateAndAnimate(DateTime date, {duration = const Duration(milliseconds: 500), curve = Curves.linear}) { + assert(_monthTimelineState != null, 'DatePickerController is not attached to any DatePicker View.'); + + _monthTimelineState!._controller.animateTo(_calculateDateOffset(date), duration: duration, curve: curve); + + if (date.compareTo(_monthTimelineState!.widget.startDate) >= 0 && + date.compareTo(_monthTimelineState!.widget.startDate.add(Duration(days: _monthTimelineState!.widget.yearCount))) <= 0) { + // date is in the range + _monthTimelineState!._currentDate = date; + } + } + + /// Calculate the number of pixels that needs to be scrolled to go to the + /// date provided in the argument + double _calculateDateOffset(DateTime date) { + final startDate = DateTime(_monthTimelineState!.widget.startDate.year, _monthTimelineState!.widget.startDate.month, + _monthTimelineState!.widget.startDate.day); + + int offset = date.difference(startDate).inDays; + return (offset * _monthTimelineState!.widget.width) + (offset * 6); + } +} diff --git a/lib/year_widget.dart b/lib/year_widget.dart new file mode 100644 index 0000000..19f752d --- /dev/null +++ b/lib/year_widget.dart @@ -0,0 +1,70 @@ +import 'package:date_picker_timeline/gestures/tap.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +class YearWidget extends StatelessWidget { + final double? width; + final DateTime month; + final TextStyle? yearTextStyle; + final Color selectionColor, iconColor; + final DateSelectionCallback? onDateSelected; + final String? locale; + final bool isSelected; + final bool showIcon; + + const YearWidget({ + required this.month, + required this.yearTextStyle, + required this.selectionColor, + required this.isSelected, + required this.iconColor, + this.width, + this.onDateSelected, + this.locale, + this.showIcon = false, + }); + + @override + Widget build(BuildContext context) { + return InkWell( + child: Container( + width: width, + margin: const EdgeInsets.all(3.0), + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + color: selectionColor, + ), + child: Padding( + padding: const EdgeInsets.all(8), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (showIcon) + Padding( + padding: const EdgeInsets.only(top: 2.0), + child: Icon( + Icons.circle, + color: iconColor, + size: isSelected ? 12 : 8, + ), + ), + Text( + "${DateFormat("y", locale).format(month).toUpperCase()}", // Month + style: yearTextStyle, + textAlign: TextAlign.center, + ), + ], + ), + ), + ), + onTap: () { + // Check if onDateSelected is not null + if (onDateSelected != null) { + // Call the onDateSelected Function + onDateSelected?.call(month); + } + }, + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 739f756..8efa242 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" boolean_selector: dependency: transitive description: @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: characters - sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" clock: dependency: transitive description: @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.18.0" fake_async: dependency: transitive description: @@ -63,50 +63,66 @@ packages: dependency: "direct main" description: name: intl - sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + url: "https://pub.dev" + source: hosted + version: "0.19.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + url: "https://pub.dev" + source: hosted + version: "10.0.4" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" url: "https://pub.dev" source: hosted - version: "0.18.1" - js: + version: "3.0.3" + leak_tracker_testing: dependency: transitive description: - name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "3.0.1" matcher: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.12.0" path: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.9.0" sky_engine: dependency: transitive description: flutter @@ -116,26 +132,26 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" string_scanner: dependency: transitive description: @@ -156,10 +172,10 @@ packages: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.7.0" vector_math: dependency: transitive description: @@ -168,6 +184,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc + url: "https://pub.dev" + source: hosted + version: "14.2.4" yaml: dependency: "direct dev" description: @@ -177,4 +201,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/pubspec.yaml b/pubspec.yaml index 704029c..4730156 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,7 +9,7 @@ environment: dependencies: flutter: sdk: flutter - intl: ^0.18.0 + intl: any dev_dependencies: flutter_test: