how to show only date number and name in CalendarTimeline() package? - flutter

I want to develope an application that help elderly like remind them medication time, and i want to add custom calendar in home screen that show the day number and name.
I have downloaded a package from internet called CalendarTimeline()
but it shows the calendar like this:
enter image description here
how can I show the custom calendar like this:
enter image description here
My code:
#override
Widget build(BuildContext context) {
return CalendarTimeline(
initialDate: DateTime(2020, 4, 20),
firstDate: DateTime(2019, 1, 15),
lastDate: DateTime(2020, 11, 20),
onDateSelected: (date) => print(date),
showYears: false,
leftMargin: 20,
monthColor: Colors.blueGrey,
dayColor: nave,
activeDayColor: nave,
activeBackgroundDayColor: Colors.yellow[100],
dotsColor: Color(0xFF333A47),
selectableDayPredicate: (date) => date.day != 23,
locale: 'en_ISO',
);
}
CalendarTimeline() class code:
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:intl/intl.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
typedef OnDateSelected = void Function(DateTime);
/// Creates a minimal, small profile calendar to select specific dates.
/// [initialDate] must not be [null], the same or after [firstDate] and
/// the same or before [lastDate]. [firstDate] must not be [null].
/// [lastDate] must not be null and the same or after [firstDate]
class CalendarTimeline extends StatefulWidget {
final DateTime initialDate;
final DateTime firstDate;
final DateTime lastDate;
final SelectableDayPredicate selectableDayPredicate;
final OnDateSelected onDateSelected;
final double leftMargin;
final Color dayColor;
final Color activeDayColor;
final Color activeBackgroundDayColor;
final Color monthColor;
final Color dotsColor;
final Color dayNameColor;
final String locale;
/// If true, it will show a separate row for the years.
/// It defaults to false
final bool showYears;
CalendarTimeline({
Key key,
#required this.initialDate,
#required this.firstDate,
#required this.lastDate,
#required this.onDateSelected,
this.selectableDayPredicate,
this.leftMargin = 0,
this.dayColor,
this.activeDayColor,
this.activeBackgroundDayColor,
this.monthColor,
this.dotsColor,
this.dayNameColor,
this.locale,
this.showYears = false,
}) : assert(initialDate != null),
assert(firstDate != null),
assert(lastDate != null),
assert(
initialDate.difference(firstDate).inDays >= 0,
'initialDate must be on or after firstDate',
),
assert(
!initialDate.isAfter(lastDate),
'initialDate must be on or before lastDate',
),
assert(
!firstDate.isAfter(lastDate),
'lastDate must be on or after firstDate',
),
assert(
selectableDayPredicate == null || selectableDayPredicate(initialDate),
'Provided initialDate must satisfy provided selectableDayPredicate',
),
assert(
locale == null || dateTimeSymbolMap().containsKey(locale),
'Provided locale value doesn\'t exist',
),
super(key: key);
#override
_CalendarTimelineState createState() => _CalendarTimelineState();
}
class _CalendarTimelineState extends State<CalendarTimeline> {
final ItemScrollController _controllerYear = ItemScrollController();
final ItemScrollController _controllerMonth = ItemScrollController();
final ItemScrollController _controllerDay = ItemScrollController();
int _yearSelectedIndex;
int _monthSelectedIndex;
int _daySelectedIndex;
double _scrollAlignment;
List<DateTime> _years = [];
List<DateTime> _months = [];
List<DateTime> _days = [];
DateTime _selectedDate;
String get _locale =>
widget.locale ?? Localizations.localeOf(context).languageCode;
/// Populates the calendar and animates to the [widget.initialDate]
#override
void initState() {
super.initState();
_initCalendar();
_scrollAlignment = widget.leftMargin / 440;
SchedulerBinding.instance.addPostFrameCallback((_) {
initializeDateFormatting(_locale);
});
}
/// Refreshes the calendar when a day, month or year is selected
#override
void didUpdateWidget(CalendarTimeline oldWidget) {
super.didUpdateWidget(oldWidget);
_initCalendar();
if (widget.showYears) _moveToYearIndex(_yearSelectedIndex);
_moveToMonthIndex(_monthSelectedIndex);
_moveToDayIndex(_daySelectedIndex);
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
if (widget.showYears) _buildYearList(),
_buildMonthList(),
_buildDayList(),
],
);
}
/// Creates the row with the day of the [selectedDate.month]. If the
/// [selectedDate.year] && [selectedDate.month] is the [widget.firstDate] or [widget.lastDate]
/// the days show will be de availables
SizedBox _buildDayList() {
return SizedBox(
height: 75,
child: ScrollablePositionedList.builder(
itemScrollController: _controllerDay,
initialScrollIndex: _daySelectedIndex,
initialAlignment: _scrollAlignment,
scrollDirection: Axis.horizontal,
itemCount: _days.length,
padding: EdgeInsets.only(left: widget.leftMargin, right: 10),
itemBuilder: (BuildContext context, int index) {
final currentDay = _days[index];
final shortName =
DateFormat.E(_locale).format(currentDay).capitalize();
return Row(
children: <Widget>[
_DayItem(
isSelected: _daySelectedIndex == index,
dayNumber: currentDay.day,
shortName: shortName.length > 3
? shortName.substring(0, 3)
: shortName,
onTap: () => _goToActualDay(index),
available: widget.selectableDayPredicate == null
? true
: widget.selectableDayPredicate(currentDay),
dayColor: widget.dayColor,
activeDayColor: widget.activeDayColor,
activeDayBackgroundColor: widget.activeBackgroundDayColor,
dotsColor: widget.dotsColor,
dayNameColor: widget.dayNameColor,
),
if (index == _days.length - 1)
SizedBox(
width: MediaQuery.of(context).size.width -
widget.leftMargin -
65)
],
);
},
),
);
}
/// Creates the row with all the months in the calendar. If [widget.showYears] is set to true
/// it will only show the months allowed in the selected year. By default it will show all
/// months in the calendar and the small version of [YearName] for each year in between
Widget _buildMonthList() {
return Container(
height: 40,
child: ScrollablePositionedList.builder(
initialScrollIndex: _monthSelectedIndex,
initialAlignment: _scrollAlignment,
itemScrollController: _controllerMonth,
padding: EdgeInsets.only(left: widget.leftMargin),
scrollDirection: Axis.horizontal,
itemCount: _months.length,
itemBuilder: (BuildContext context, int index) {
final currentDate = _months[index];
final monthName = DateFormat.MMMM(_locale).format(currentDate);
return Padding(
padding: const EdgeInsets.only(right: 12.0, left: 4.0),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
if (widget.firstDate.year != currentDate.year &&
currentDate.month == 1 &&
!widget.showYears)
Padding(
padding: const EdgeInsets.only(right: 10),
child: YearName(
name: DateFormat.y(_locale).format(currentDate),
color: widget.monthColor,
),
),
MonthName(
isSelected: _monthSelectedIndex == index,
name: monthName,
onTap: () => _goToActualMonth(index),
color: widget.monthColor,
),
if (index == _months.length - 1)
SizedBox(
width: MediaQuery.of(context).size.width -
widget.leftMargin -
(monthName.length * 10),
)
],
),
);
},
),
);
}
/// Creates the row with all the years in the calendar. It will only show if
/// [widget.showYears] is set to true. It is false by default
Widget _buildYearList() {
return Container(
height: 40,
child: ScrollablePositionedList.builder(
initialScrollIndex: _yearSelectedIndex,
initialAlignment: _scrollAlignment,
itemScrollController: _controllerYear,
padding: EdgeInsets.only(left: widget.leftMargin),
scrollDirection: Axis.horizontal,
itemCount: _years.length,
itemBuilder: (BuildContext context, int index) {
final currentDate = _years[index];
final yearName = DateFormat.y(_locale).format(currentDate);
return Padding(
padding: const EdgeInsets.only(right: 12.0, left: 4.0),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
YearName(
isSelected: _yearSelectedIndex == index,
name: yearName,
onTap: () => _goToActualYear(index),
color: widget.monthColor,
small: false,
),
if (index == _years.length - 1)
SizedBox(
width: MediaQuery.of(context).size.width -
widget.leftMargin -
(yearName.length * 10),
)
],
),
);
},
),
);
}
/// It will populate the [_days] list with all the allowed days. Adding all days of the month
/// when the [selectedDate.month] is not the first or the last in [widget.firstDate] or [widget.lastDate].
/// In that case it will only show the allowed days from and up to the specified in [widget.firstDate]
/// and [widget.lastDate]
_generateDays(DateTime selectedDate) {
_days.clear();
for (var i = 1; i <= 31; i++) {
final day = DateTime(selectedDate.year, selectedDate.month, i);
if (day.difference(widget.firstDate).inDays < 0) continue;
if (day.month != selectedDate.month || day.isAfter(widget.lastDate))
break;
_days.add(day);
}
}
/// It will populate the [_months] list. If [widget.showYears] is true, it will add from January
/// to December, unless the selected year is the [widget.firstDate.year] or the [widget.lastDate.year].
/// In that case it will only from and up to the allowed months in [widget.firstDate] and [widget.lastDate].
/// By default, when [widget.showYears] is false, it will add all months from [widget.firstDate] to
/// [widget.lastDate] and all in between
_generateMonths(DateTime selectedDate) {
_months.clear();
if (widget.showYears) {
int month = selectedDate.year == widget.firstDate.year
? widget.firstDate.month
: 1;
DateTime date = DateTime(selectedDate.year, month);
while (date.isBefore(DateTime(selectedDate.year + 1)) &&
date.isBefore(widget.lastDate)) {
_months.add(date);
date = DateTime(date.year, date.month + 1);
}
} else {
DateTime date = DateTime(widget.firstDate.year, widget.firstDate.month);
while (date.isBefore(widget.lastDate)) {
_months.add(date);
date = DateTime(date.year, date.month + 1);
}
}
}
/// It will populate the [_years] list with the years between firstDate and lastDate
_generateYears() {
_years.clear();
DateTime date = widget.firstDate;
while (date.isBefore(widget.lastDate)) {
_years.add(date);
date = DateTime(date.year + 1);
}
}
/// It will reset the calendar to the initial date
_resetCalendar(DateTime date) {
if (widget.showYears) _generateMonths(date);
_generateDays(date);
_daySelectedIndex = date.month == _selectedDate.month
? _days.indexOf(
_days.firstWhere((dayDate) => dayDate.day == _selectedDate.day))
: null;
_controllerDay.scrollTo(
index: _daySelectedIndex ?? 0,
alignment: _scrollAlignment,
duration: Duration(milliseconds: 500),
curve: Curves.easeIn,
);
}
_goToActualYear(int index) {
_moveToYearIndex(index);
_yearSelectedIndex = index;
_resetCalendar(_years[index]);
setState(() {});
}
void _moveToYearIndex(int index) {
_controllerYear.scrollTo(
index: index,
alignment: _scrollAlignment,
duration: Duration(milliseconds: 500),
curve: Curves.easeIn,
);
}
_goToActualMonth(int index) {
_moveToMonthIndex(index);
_monthSelectedIndex = index;
_resetCalendar(_months[index]);
setState(() {});
}
void _moveToMonthIndex(int index) {
_controllerMonth.scrollTo(
index: index,
alignment: _scrollAlignment,
duration: Duration(milliseconds: 500),
curve: Curves.easeIn,
);
}
_goToActualDay(int index) {
_moveToDayIndex(index);
_daySelectedIndex = index;
_selectedDate = _days[index];
widget.onDateSelected(_selectedDate);
setState(() {});
}
void _moveToDayIndex(int index) {
_controllerDay.scrollTo(
index: index,
alignment: _scrollAlignment,
duration: Duration(milliseconds: 500),
curve: Curves.easeIn,
);
}
selectedYear() {
_yearSelectedIndex = _years.indexOf(_years
.firstWhere((yearDate) => yearDate.year == widget.initialDate.year));
}
selectedMonth() {
if (widget.showYears)
_monthSelectedIndex = _months.indexOf(_months.firstWhere(
(monthDate) => monthDate.month == widget.initialDate.month));
else
_monthSelectedIndex = _months.indexOf(_months.firstWhere((monthDate) =>
monthDate.year == widget.initialDate.year &&
monthDate.month == widget.initialDate.month));
}
selectedDay() {
_daySelectedIndex = _days.indexOf(
_days.firstWhere((dayDate) => dayDate.day == widget.initialDate.day));
}
/// Initializes the calendar. It will be executed every time a new date is selected
_initCalendar() {
_selectedDate = widget.initialDate;
_generateMonths(_selectedDate);
_generateDays(_selectedDate);
if (widget.showYears) {
_generateYears();
selectedYear();
}
selectedMonth();
selectedDay();
}
}
/// Creates a Widget to represent the years. By default it will show the smaller version
/// in the months row. If [small] is set to false it will show the bigger version for the
/// years row. In the smaller version the [onTap] property is not available
class YearName extends StatelessWidget {
final String name;
final Function onTap;
final bool isSelected;
final Color color;
final bool small;
YearName(
{this.name,
this.onTap,
this.isSelected = false,
this.color,
this.small = true});
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: small ? null : onTap,
child: Container(
decoration: isSelected || small
? BoxDecoration(
border: Border.all(color: color ?? Colors.black87, width: 1),
borderRadius: BorderRadius.circular(4),
)
: null,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 14.0, vertical: 5.0),
child: Text(
name.toUpperCase(),
style: TextStyle(
fontSize: small ? 12 : 20,
color: color ?? Colors.black87,
fontWeight: FontWeight.bold,
),
),
),
),
);
}
}
/// Creates a Widget to represent the monts.
class MonthName extends StatelessWidget {
final String name;
final Function onTap;
final bool isSelected;
final Color color;
MonthName({this.name, this.onTap, this.isSelected, this.color});
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: this.onTap,
child: Text(
this.name.toUpperCase(),
style: TextStyle(
fontSize: 14,
color: color ?? Colors.black87,
fontWeight: this.isSelected ? FontWeight.bold : FontWeight.w300,
),
),
);
}
}
/// Creates a Widget representing the day.
class _DayItem extends StatelessWidget {
final int dayNumber;
final String shortName;
final bool isSelected;
final Function onTap;
final Color dayColor;
final Color activeDayColor;
final Color activeDayBackgroundColor;
final bool available;
final Color dotsColor;
final Color dayNameColor;
const _DayItem({
Key key,
#required this.dayNumber,
#required this.shortName,
#required this.isSelected,
#required this.onTap,
this.dayColor,
this.activeDayColor,
this.activeDayBackgroundColor,
this.available = true,
this.dotsColor,
this.dayNameColor,
}) : super(key: key);
final double height = 70.0;
final double width = 60.0;
///? I united both widgets to increase the touch target of non selected days by using a transparent box decorator.
///? Now if the user click close to the number but not straight on top it will still select the date. (ONLY INFORMATION - ERASE)
_buildDay(BuildContext context) {
final textStyle = TextStyle(
color: available
? dayColor ?? Theme.of(context).accentColor
: dayColor?.withOpacity(0.5) ??
Theme.of(context).accentColor.withOpacity(0.5),
fontSize: 32,
fontWeight: FontWeight.normal);
final selectedStyle = TextStyle(
color: activeDayColor ?? Colors.white,
fontSize: 32,
fontWeight: FontWeight.bold,
height: 0.8,
);
return GestureDetector(
onTap: available ? onTap : null,
child: Container(
decoration: isSelected
? BoxDecoration(
color:
activeDayBackgroundColor ?? Theme.of(context).accentColor,
borderRadius: BorderRadius.circular(12.0),
)
: BoxDecoration(color: Colors.transparent),
height: height,
width: width,
child: Column(
children: <Widget>[
if (isSelected) ...[
SizedBox(height: 7),
_buildDots(),
SizedBox(height: 12),
] else
SizedBox(height: 14),
Text(
dayNumber.toString(),
style: isSelected ? selectedStyle : textStyle,
),
if (isSelected)
Text(
shortName,
style: TextStyle(
color: dayNameColor ?? activeDayColor ?? Colors.white,
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
],
),
),
);
}
Widget _buildDots() {
final dot = Container(
height: 5,
width: 5,
decoration: new BoxDecoration(
color: this.dotsColor ?? this.activeDayColor ?? Colors.white,
shape: BoxShape.circle,
),
);
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [dot, dot],
);
}
#override
Widget build(BuildContext context) {
return _buildDay(context);
}
}
extension StringExtension on String {
String capitalize() {
if (this.isEmpty) {
return this;
}
return this[0].toUpperCase() + this.substring(1);
}
}

Is this more close to what you are looking for?
DatePicker(
DateTime.now(),
initialSelectedDate: DateTime.now(),
selectionColor: Colors.black,
selectedTextColor: Colors.white,
onDateChange: (date) {
// New date selected
setState(() {
_selectedValue = date;
});
},
),

Related

Change stepper's line color flutter

I want to change color of line in stepper, Look at the below picture
my try here :
Theme(
data: ThemeData(canvasColor: whiteColor),
child: Stepper(
type: StepperType.horizontal,
steps: getSteps(),
currentStep: currentStep,
elevation: 2,
),
),
Please help to continue my code.
Let me be short and clear.
TO CHANGE STEPPER LINE COLOR
Unfortunately, the Flutter code for the Stepper does not support the change of the Stepper line color (_buildLine) and more.
So I created a custom stepper called CusStepper that uses CusStep
NOTE: This CusStepper has the same properties and behaves like the normal Stepper with the only difference of lineColor property.
Steps on how to use the CusStepper
Create a new .dart file to store the CusStepper code
Paste the code below for the CusStepper in the file.
Import it to the file you want to use it in.
Finally change the stepper line color with the lineColor params. on the CusStepper
CusStepper code to paste in the .dart file created in step 1
// THIS IS A CUSTOM STEPPER THAT ADDS LINE COLOR
import 'package:flutter/material.dart';
enum CusStepState {
indexed,
editing,
complete,
disabled,
error,
}
enum CusStepperType {
vertical,
horizontal,
}
#immutable
class ControlsDetails {
const ControlsDetails({
required this.currentStep,
required this.stepIndex,
this.onStepCancel,
this.onStepContinue,
});
final int currentStep;
final int stepIndex;
final VoidCallback? onStepContinue;
final VoidCallback? onStepCancel;
bool get isActive => currentStep == stepIndex;
}
typedef ControlsWidgetBuilder = Widget Function(
BuildContext context, ControlsDetails details);
const TextStyle _kStepStyle = TextStyle(
fontSize: 12.0,
color: Colors.white,
);
const Color _kErrorLight = Colors.red;
final Color _kErrorDark = Colors.red.shade400;
const Color _kCircleActiveLight = Colors.white;
const Color _kCircleActiveDark = Colors.black87;
const Color _kDisabledLight = Colors.black38;
const Color _kDisabledDark = Colors.white38;
const double _kStepSize = 24.0;
const double _kTriangleHeight = _kStepSize * 0.866025;
#immutable
class CusStep {
const CusStep({
required this.title,
this.subtitle,
required this.content,
this.state = CusStepState.indexed,
this.isActive = false,
this.label,
});
final Widget title;
final Widget? subtitle;
final Widget content;
final CusStepState state;
final bool isActive;
final Widget? label;
}
class CusStepper extends StatefulWidget {
const CusStepper({
super.key,
required this.steps,
this.physics,
this.type = CusStepperType.vertical,
this.currentStep = 0,
this.onStepTapped,
this.onStepContinue,
this.onStepCancel,
this.controlsBuilder,
this.elevation,
this.margin,
this.lineColor = Colors.grey,
}) : assert(0 <= currentStep && currentStep < steps.length);
final List<CusStep> steps;
final ScrollPhysics? physics;
final CusStepperType type;
final int currentStep;
final ValueChanged<int>? onStepTapped;
final VoidCallback? onStepContinue;
final VoidCallback? onStepCancel;
final ControlsWidgetBuilder? controlsBuilder;
final double? elevation;
final EdgeInsetsGeometry? margin;
final Color lineColor;
#override
State<CusStepper> createState() => _CusStepperState();
}
class _CusStepperState extends State<CusStepper> with TickerProviderStateMixin {
late List<GlobalKey> _keys;
final Map<int, CusStepState> _oldStates = <int, CusStepState>{};
#override
void initState() {
super.initState();
_keys = List<GlobalKey>.generate(
widget.steps.length,
(int i) => GlobalKey(),
);
for (int i = 0; i < widget.steps.length; i += 1) {
_oldStates[i] = widget.steps[i].state;
}
}
#override
void didUpdateWidget(CusStepper oldWidget) {
super.didUpdateWidget(oldWidget);
assert(widget.steps.length == oldWidget.steps.length);
for (int i = 0; i < oldWidget.steps.length; i += 1) {
_oldStates[i] = oldWidget.steps[i].state;
}
}
bool _isFirst(int index) {
return index == 0;
}
bool _isLast(int index) {
return widget.steps.length - 1 == index;
}
bool _isCurrent(int index) {
return widget.currentStep == index;
}
bool _isDark() {
return Theme.of(context).brightness == Brightness.dark;
}
bool _isLabel() {
for (final CusStep step in widget.steps) {
if (step.label != null) {
return true;
}
}
return false;
}
Widget _buildLine(bool visible) {
return Container(
width: visible ? 1.0 : 0.0,
height: 16.0,
color: widget.lineColor,
);
}
Widget _buildCircleChild(int index, bool oldState) {
final CusStepState state =
oldState ? _oldStates[index]! : widget.steps[index].state;
final bool isDarkActive = _isDark() && widget.steps[index].isActive;
switch (state) {
case CusStepState.indexed:
case CusStepState.disabled:
return Text(
'${index + 1}',
style: isDarkActive
? _kStepStyle.copyWith(color: Colors.black87)
: _kStepStyle,
);
case CusStepState.editing:
return Icon(
Icons.edit,
color: isDarkActive ? _kCircleActiveDark : _kCircleActiveLight,
size: 18.0,
);
case CusStepState.complete:
return Icon(
Icons.check,
color: isDarkActive ? _kCircleActiveDark : _kCircleActiveLight,
size: 18.0,
);
case CusStepState.error:
return const Text('!', style: _kStepStyle);
}
}
Color _circleColor(int index) {
final ColorScheme colorScheme = Theme.of(context).colorScheme;
if (!_isDark()) {
return widget.steps[index].isActive
? colorScheme.primary
: colorScheme.onSurface.withOpacity(0.38);
} else {
return widget.steps[index].isActive
? colorScheme.secondary
: colorScheme.background;
}
}
Widget _buildCircle(int index, bool oldState) {
return Container(
margin: const EdgeInsets.symmetric(vertical: 8.0),
width: _kStepSize,
height: _kStepSize,
child: AnimatedContainer(
curve: Curves.fastOutSlowIn,
duration: kThemeAnimationDuration,
decoration: BoxDecoration(
color: _circleColor(index),
shape: BoxShape.circle,
),
child: Center(
child: _buildCircleChild(
index, oldState && widget.steps[index].state == CusStepState.error),
),
),
);
}
Widget _buildTriangle(int index, bool oldState) {
return Container(
margin: const EdgeInsets.symmetric(vertical: 8.0),
width: _kStepSize,
height: _kStepSize,
child: Center(
child: SizedBox(
width: _kStepSize,
height:
_kTriangleHeight, // Height of 24dp-long-sided equilateral triangle.
child: CustomPaint(
painter: _TrianglePainter(
color: _isDark() ? _kErrorDark : _kErrorLight,
),
child: Align(
alignment: const Alignment(
0.0, 0.8), // 0.8 looks better than the geometrical 0.33.
child: _buildCircleChild(index,
oldState && widget.steps[index].state != CusStepState.error),
),
),
),
),
);
}
Widget _buildIcon(int index) {
if (widget.steps[index].state != _oldStates[index]) {
return AnimatedCrossFade(
firstChild: _buildCircle(index, true),
secondChild: _buildTriangle(index, true),
firstCurve: const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn),
secondCurve: const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn),
sizeCurve: Curves.fastOutSlowIn,
crossFadeState: widget.steps[index].state == CusStepState.error
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
duration: kThemeAnimationDuration,
);
} else {
if (widget.steps[index].state != CusStepState.error) {
return _buildCircle(index, false);
} else {
return _buildTriangle(index, false);
}
}
}
Widget _buildVerticalControls(int stepIndex) {
if (widget.controlsBuilder != null) {
return widget.controlsBuilder!(
context,
ControlsDetails(
currentStep: widget.currentStep,
onStepContinue: widget.onStepContinue,
onStepCancel: widget.onStepCancel,
stepIndex: stepIndex,
),
);
}
final Color cancelColor;
switch (Theme.of(context).brightness) {
case Brightness.light:
cancelColor = Colors.black54;
break;
case Brightness.dark:
cancelColor = Colors.white70;
break;
}
final ThemeData themeData = Theme.of(context);
final ColorScheme colorScheme = themeData.colorScheme;
final MaterialLocalizations localizations =
MaterialLocalizations.of(context);
const OutlinedBorder buttonShape = RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(2)));
const EdgeInsets buttonPadding = EdgeInsets.symmetric(horizontal: 16.0);
return Container(
margin: const EdgeInsets.only(top: 16.0),
child: ConstrainedBox(
constraints: const BoxConstraints.tightFor(height: 48.0),
child: Row(
children: <Widget>[
TextButton(
onPressed: widget.onStepContinue,
style: ButtonStyle(
foregroundColor: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
return states.contains(MaterialState.disabled)
? null
: (_isDark()
? colorScheme.onSurface
: colorScheme.onPrimary);
}),
backgroundColor: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
return _isDark() || states.contains(MaterialState.disabled)
? null
: colorScheme.primary;
}),
padding: const MaterialStatePropertyAll<EdgeInsetsGeometry>(
buttonPadding),
shape:
const MaterialStatePropertyAll<OutlinedBorder>(buttonShape),
),
child: Text(themeData.useMaterial3
? localizations.continueButtonLabel
: localizations.continueButtonLabel.toUpperCase()),
),
Container(
margin: const EdgeInsetsDirectional.only(start: 8.0),
child: TextButton(
onPressed: widget.onStepCancel,
style: TextButton.styleFrom(
foregroundColor: cancelColor,
padding: buttonPadding,
shape: buttonShape,
),
child: Text(themeData.useMaterial3
? localizations.cancelButtonLabel
: localizations.cancelButtonLabel.toUpperCase()),
),
),
],
),
),
);
}
TextStyle _titleStyle(int index) {
final ThemeData themeData = Theme.of(context);
final TextTheme textTheme = themeData.textTheme;
switch (widget.steps[index].state) {
case CusStepState.indexed:
case CusStepState.editing:
case CusStepState.complete:
return textTheme.bodyLarge!;
case CusStepState.disabled:
return textTheme.bodyLarge!.copyWith(
color: _isDark() ? _kDisabledDark : _kDisabledLight,
);
case CusStepState.error:
return textTheme.bodyLarge!.copyWith(
color: _isDark() ? _kErrorDark : _kErrorLight,
);
}
}
TextStyle _subtitleStyle(int index) {
final ThemeData themeData = Theme.of(context);
final TextTheme textTheme = themeData.textTheme;
switch (widget.steps[index].state) {
case CusStepState.indexed:
case CusStepState.editing:
case CusStepState.complete:
return textTheme.bodySmall!;
case CusStepState.disabled:
return textTheme.bodySmall!.copyWith(
color: _isDark() ? _kDisabledDark : _kDisabledLight,
);
case CusStepState.error:
return textTheme.bodySmall!.copyWith(
color: _isDark() ? _kErrorDark : _kErrorLight,
);
}
}
TextStyle _labelStyle(int index) {
final ThemeData themeData = Theme.of(context);
final TextTheme textTheme = themeData.textTheme;
switch (widget.steps[index].state) {
case CusStepState.indexed:
case CusStepState.editing:
case CusStepState.complete:
return textTheme.bodyLarge!;
case CusStepState.disabled:
return textTheme.bodyLarge!.copyWith(
color: _isDark() ? _kDisabledDark : _kDisabledLight,
);
case CusStepState.error:
return textTheme.bodyLarge!.copyWith(
color: _isDark() ? _kErrorDark : _kErrorLight,
);
}
}
Widget _buildHeaderText(int index) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
AnimatedDefaultTextStyle(
style: _titleStyle(index),
duration: kThemeAnimationDuration,
curve: Curves.fastOutSlowIn,
child: widget.steps[index].title,
),
if (widget.steps[index].subtitle != null)
Container(
margin: const EdgeInsets.only(top: 2.0),
child: AnimatedDefaultTextStyle(
style: _subtitleStyle(index),
duration: kThemeAnimationDuration,
curve: Curves.fastOutSlowIn,
child: widget.steps[index].subtitle!,
),
),
],
);
}
Widget _buildLabelText(int index) {
if (widget.steps[index].label != null) {
return AnimatedDefaultTextStyle(
style: _labelStyle(index),
duration: kThemeAnimationDuration,
child: widget.steps[index].label!,
);
}
return const SizedBox.shrink();
}
Widget _buildVerticalHeader(int index) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 24.0),
child: Row(
children: <Widget>[
Column(
children: <Widget>[
_buildLine(!_isFirst(index)),
_buildIcon(index),
_buildLine(!_isLast(index)),
],
),
Expanded(
child: Container(
margin: const EdgeInsetsDirectional.only(start: 12.0),
child: _buildHeaderText(index),
),
),
],
),
);
}
Widget _buildVerticalBody(int index) {
return Stack(
children: <Widget>[
PositionedDirectional(
start: 24.0,
top: 0.0,
bottom: 0.0,
child: SizedBox(
width: 24.0,
child: Center(
child: SizedBox(
width: _isLast(index) ? 0.0 : 1.0,
child: Container(
color: widget.lineColor,
),
),
),
),
),
AnimatedCrossFade(
firstChild: Container(height: 0.0),
secondChild: Container(
margin: widget.margin ??
const EdgeInsetsDirectional.only(
start: 60.0,
end: 24.0,
bottom: 24.0,
),
child: Column(
children: <Widget>[
widget.steps[index].content,
_buildVerticalControls(index),
],
),
),
firstCurve: const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn),
secondCurve: const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn),
sizeCurve: Curves.fastOutSlowIn,
crossFadeState: _isCurrent(index)
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
duration: kThemeAnimationDuration,
),
],
);
}
Widget _buildVertical() {
return ListView(
shrinkWrap: true,
physics: widget.physics,
children: <Widget>[
for (int i = 0; i < widget.steps.length; i += 1)
Column(
key: _keys[i],
children: <Widget>[
InkWell(
onTap: widget.steps[i].state != CusStepState.disabled
? () {
// In the vertical case we need to scroll to the newly tapped
// step.
Scrollable.ensureVisible(
_keys[i].currentContext!,
curve: Curves.fastOutSlowIn,
duration: kThemeAnimationDuration,
);
widget.onStepTapped?.call(i);
}
: null,
canRequestFocus: widget.steps[i].state != CusStepState.disabled,
child: _buildVerticalHeader(i),
),
_buildVerticalBody(i),
],
),
],
);
}
Widget _buildHorizontal() {
final List<Widget> children = <Widget>[
for (int i = 0; i < widget.steps.length; i += 1) ...<Widget>[
InkResponse(
onTap: widget.steps[i].state != CusStepState.disabled
? () {
widget.onStepTapped?.call(i);
}
: null,
canRequestFocus: widget.steps[i].state != CusStepState.disabled,
child: Row(
children: <Widget>[
SizedBox(
height: _isLabel() ? 104.0 : 72.0,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
if (widget.steps[i].label != null)
const SizedBox(
height: 24.0,
),
Center(child: _buildIcon(i)),
if (widget.steps[i].label != null)
SizedBox(
height: 24.0,
child: _buildLabelText(i),
),
],
),
),
Container(
margin: const EdgeInsetsDirectional.only(start: 12.0),
child: _buildHeaderText(i),
),
],
),
),
if (!_isLast(i))
Expanded(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 8.0),
height: 1.0,
color: widget.lineColor,
),
),
],
];
final List<Widget> stepPanels = <Widget>[];
for (int i = 0; i < widget.steps.length; i += 1) {
stepPanels.add(
Visibility(
maintainState: true,
visible: i == widget.currentStep,
child: widget.steps[i].content,
),
);
}
return Column(
children: <Widget>[
Material(
elevation: widget.elevation ?? 2,
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 24.0),
child: Row(
children: children,
),
),
),
Expanded(
child: ListView(
physics: widget.physics,
padding: const EdgeInsets.all(24.0),
children: <Widget>[
AnimatedSize(
curve: Curves.fastOutSlowIn,
duration: kThemeAnimationDuration,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: stepPanels),
),
_buildVerticalControls(widget.currentStep),
],
),
),
],
);
}
#override
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
assert(debugCheckHasMaterialLocalizations(context));
assert(() {
if (context.findAncestorWidgetOfExactType<Stepper>() != null) {
throw FlutterError(
'Steppers must not be nested.\n'
'The material specification advises that one should avoid embedding '
'steppers within steppers. '
'https://material.io/archive/guidelines/components/steppers.html#steppers-usage',
);
}
return true;
}());
switch (widget.type) {
case CusStepperType.vertical:
return _buildVertical();
case CusStepperType.horizontal:
return _buildHorizontal();
}
}
}
class _TrianglePainter extends CustomPainter {
_TrianglePainter({
required this.color,
});
final Color color;
#override
bool hitTest(Offset point) => true;
#override
bool shouldRepaint(_TrianglePainter oldPainter) {
return oldPainter.color != color;
}
#override
void paint(Canvas canvas, Size size) {
final double base = size.width;
final double halfBase = size.width / 2.0;
final double height = size.height;
final List<Offset> points = <Offset>[
Offset(0.0, height),
Offset(base, height),
Offset(halfBase, 0.0),
];
canvas.drawPath(
Path()..addPolygon(points, true),
Paint()..color = color,
);
}
}
EXAMPLE CODEBASE ON HOW TO USE THE CusStepper
import 'package:flutter/material.dart';
import 'package:tester/stepper/stepper.dart'; // this is the file path of where you store your `cusStepper`
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(const App());
}
class App extends StatelessWidget {
const App({super.key});
#override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Tester',
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int _index = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: CusStepper(
lineColor: Colors.red, // new line add for the color change
currentStep: _index,
onStepCancel: () {
if (_index > 0) {
setState(() {
_index -= 1;
});
}
},
onStepContinue: () {
if (_index <= 0) {
setState(() {
_index += 1;
});
}
},
onStepTapped: (int index) {
setState(() {
_index = index;
});
},
steps: <CusStep>[
CusStep(
title: const Text('Step 1 title'),
content: Container(
alignment: Alignment.centerLeft,
child: const Text('Content for Step 1')),
),
const CusStep(
title: Text('Step 2 title'),
content: Text('Content for Step 2'),
),
],
),
),
);
}
}
OUTPUT
Leave a comment below if you have any questions or help on this.
Bye!
The Flutter Default stepper Line has Static color use so can't change this.
chack below Image.
Here https://fluttergems.dev/stepper mention many stepper package used as you want.

Unsupported operation: Nan or infinity toInt for Tween Animation

I have found a code that i tried to replicate. It has an app itself and it is working. However when i tried to write the code down it shows the problem Unsupported operation NaN or infinity toInt for a function named TweenAnimation. I am still trying to learn how to use tween animation so any pros have any idea on this problem?
below is the code for the animation
import 'package:flutter/material.dart';
class AnimatedFlipCounter extends StatelessWidget {
final int value;
final Duration duration;
final double size;
final Color color;
const AnimatedFlipCounter({
Key? key,
required this.value,
required this.duration,
this.size = 72,
this.color = Colors.black,
}) : super(key: key);
#override
Widget build(BuildContext context) {
List<int> digits = value == 0 ? [0] : [];
int v = value;
if (v < 0) {
v *= -1;
}
while (v > 0) {
digits.add(v);
v = v ~/ 10;
}
return Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(digits.length, (int i) {
return _SingleDigitFlipCounter(
key: ValueKey(digits.length - i),
value: digits[digits.length - i - 1].toDouble(),
duration: duration,
height: size,
width: size / 1.8,
color: color,
);
}),
);
}
}
class _SingleDigitFlipCounter extends StatelessWidget {
final double value;
final Duration duration;
final double height;
final double width;
final Color color;
const _SingleDigitFlipCounter({
required Key key,
required this.value,
required this.duration,
required this.height,
required this.width,
required this.color,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return TweenAnimationBuilder(
tween: Tween(begin: value, end: value),
duration: duration,
builder: (context,double value, child) {
final whole = 1.0 ~/ value ;
final decimal = value - whole;
return SizedBox(
height: height,
width: width,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
_buildSingleDigit(
digit: whole % 10,
offset: height * decimal,
opacity: 1.0 - decimal,
),
_buildSingleDigit(
digit: (whole + 1) % 10,
offset: height * decimal - height,
opacity: decimal,
),
],
),
);
},
);
}
Widget _buildSingleDigit({required int digit, required double offset, required double opacity}) {
return Positioned(
child: SizedBox(
width: width,
child: Opacity(
opacity: opacity,
child: Text(
"$digit",
style: TextStyle(
fontSize: height, color: color, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
),
),
bottom: offset,
);
}
below is the main page that calls this widget
import 'package:carousel_slider/carousel_slider.dart';
import 'package:daily5/helpers/constants_tasbih.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
import 'package:get_storage/get_storage.dart';
import 'package:vibration/vibration.dart';
import '../../helpers/animated_flip_counter.dart';
class Tasbih extends StatefulWidget {
const Tasbih({Key? key}) : super(key: key);
#override
_TasbihState createState() => _TasbihState();
}
class _TasbihState extends State<Tasbih> {
final PageController _controller =
PageController(viewportFraction: 0.1, initialPage: 5);
final int _numberOfCountsToCompleteRound = 33;
int _imageIndex = 1;
int _beadCounter = 0;
int _roundCounter = 0;
int _accumulatedCounter = 0;
bool _canVibrate = true;
bool _isDisposed = false;
final List<Color> _bgColour = [
Colors.teal.shade50,
Colors.lime.shade50,
Colors.lightBlue.shade50,
Colors.pink.shade50,
Colors.black12
];
final CarouselController _buttonCarouselController = CarouselController();
#override
void initState() {
super.initState();
_loadSettings();
}
#override
void dispose() {
_isDisposed = true;
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onTap: _clicked,
onVerticalDragStart: (_) => _clicked(),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Expanded(
flex: 2,
child: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Row(
children: [
const SizedBox(width: 45),
IconButton(
tooltip: 'Change colour',
icon: const Icon(Icons.palette),
onPressed: () {
setState(() {
_imageIndex < 5
? _imageIndex++
: _imageIndex = 1;
});
}),
IconButton(
tooltip: 'Reset counter',
icon: const Icon(Icons.refresh),
onPressed: () {
confirmReset(context, _resetEverything);
}),
],
),
const Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
textDirection: TextDirection.ltr,
children: <Widget>[
_Counter(
counter: _roundCounter, counterName: 'Round'),
_Counter(counter: _beadCounter, counterName: 'Beads'),
],
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 32),
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Text('Accumulated'),
const SizedBox(width: 10),
AnimatedFlipCounter(
value: _accumulatedCounter,
duration: const Duration(milliseconds: 730),
size: 14),
],
),
),
CarouselSlider(
carouselController: _buttonCarouselController,
options: CarouselOptions(
height: 100.0,
enlargeCenterPage: true,
),
items: [1, 2, 3, 4].map((i) {
return Builder(
builder: (BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
margin: const EdgeInsets.symmetric(
horizontal: 5.0),
decoration: BoxDecoration(
color: _bgColour[_imageIndex - 1],
borderRadius: BorderRadius.circular(12)),
child: Image.asset('assets/images/zikr/$i.png'));
},
);
}).toList(),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
onPressed: () {
_buttonCarouselController.previousPage();
},
icon: const Icon(Icons.navigate_before)),
IconButton(
onPressed: () {
_buttonCarouselController.nextPage();
},
icon: const Icon(Icons.navigate_next)),
],
),
const Spacer()
],
),
)),
Expanded(
flex: 1,
child: PageView.builder(
reverse: true,
physics: const NeverScrollableScrollPhysics(),
controller: _controller,
scrollDirection: Axis.vertical,
itemBuilder: (_, __) {
return Image.asset(
'assets/images/beads/bead-$_imageIndex.png',
);
},
itemCount: null,
),
),
],
),
),
);
}
void _loadSettings() async {
bool? canVibrate = await Vibration.hasVibrator();
if (!_isDisposed) {
setState(() {
_canVibrate = canVibrate!;
_loadData();
});
}
}
void _loadData() {
if (!_isDisposed) {
setState(() {
_beadCounter = GetStorage().read(kBeadsCount) ?? 0;
_roundCounter = GetStorage().read(kRoundCount) ?? 0;
_accumulatedCounter =
_roundCounter * _numberOfCountsToCompleteRound + _beadCounter;
});
}
}
void _resetEverything() {
GetStorage().write(kBeadsCount, 0);
GetStorage().write(kRoundCount, 0);
_loadData();
}
void _clicked() {
if (!_isDisposed) {
setState(() {
_beadCounter++;
_accumulatedCounter++;
if (_beadCounter > _numberOfCountsToCompleteRound) {
_beadCounter = 1;
_roundCounter++;
if (_canVibrate) Vibration.vibrate(duration: 100, amplitude: 100);
}
});
}
GetStorage().write(kBeadsCount, _beadCounter);
GetStorage().write(kRoundCount, _roundCounter);
int nextPage = _controller.page!.round() + 1;
_controller.animateToPage(nextPage,
duration: const Duration(milliseconds: 200), curve: Curves.easeIn);
}
}
class _Counter extends StatelessWidget {
const _Counter(
{Key? key,
required this.counter,
this.tsCounter =
const TextStyle(fontSize: 50, fontWeight: FontWeight.bold),
required this.counterName,
this.tsCounterName = const TextStyle(
fontSize: 20,
fontStyle: FontStyle.italic,
fontWeight: FontWeight.w300)})
: super(key: key);
final int counter;
final TextStyle tsCounter;
final String counterName;
final TextStyle tsCounterName;
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
AnimatedFlipCounter(
duration: const Duration(milliseconds: 300),
value: counter,
),
Text(counterName, style: tsCounterName)
],
);
}
}
void confirmReset(BuildContext context, VoidCallback callback) {
const _confirmText = Text('Confirm', style: TextStyle(color: Colors.red));
const _cancelText = Text('Cancel');
const _dialogTitle = Text("Reset Counter?");
const _dialogContent = Text("This action can't be undone");
void _confirmResetAction() {
callback();
showSnackBar(
context: context,
label: 'Cleared',
icon: CupertinoIcons.check_mark_circled);
Navigator.of(context).pop();
}
showDialog(
context: context,
builder: (_) {
return kIsWeb
? AlertDialog(
title: _dialogTitle,
content: _dialogContent,
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: _cancelText,
),
TextButton(
onPressed: _confirmResetAction,
child: _confirmText,
),
],
)
: CupertinoAlertDialog(
title: _dialogTitle,
content: _dialogContent,
actions: [
CupertinoDialogAction(
child: _cancelText,
onPressed: () => Navigator.of(context).pop(),
),
CupertinoDialogAction(
child: _confirmText,
onPressed: _confirmResetAction,
),
],
);
},
);
}
void showSnackBar({required BuildContext context, required String label, required IconData icon}) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
behavior: SnackBarBehavior.floating,
content: Row(
children: [
Icon(
icon,
color: Colors.white60,
),
const SizedBox(width: 5),
Text(label)
],
),
),
);
}
Supposedly it should beads that can be counted down but the animation cant load.

how to solve "The method 'call' was called on null. Receiver: null Tried calling: call()" error on flutter?

i am trying to integrate stripe payment method in flutter. i am following the tutorial from https://www.youtube.com/watch?v=VOtmM9i25R4.
i got error that says The method 'call' was called on null.
Receiver: null
Tried calling: call()
this is the code where the error is
import 'package:flutter/material.dart';
import 'package:flutter_credit_card/credit_card_widget.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:izzilly_customer/API_service/payment_service/stripe-
payment-service.dart';
import 'package:stripe_payment/stripe_payment.dart';
class ExistingCardsPage extends StatefulWidget {
static const String id = 'existing-card';
ExistingCardsPage({Key key}) : super(key: key);
#override
ExistingCardsPageState createState() => ExistingCardsPageState();
}
class ExistingCardsPageState extends State<ExistingCardsPage> {
List cards = [{
'cardNumber': '4242424242424242',
'expiryDate': '04/24',
'cardHolderName': 'Muhammad Ahsan Ayaz',
'cvvCode': '424',
'showBackView': false,
}, {
'cardNumber': '5555555566554444',
'expiryDate': '04/23',
'cardHolderName': 'Tracer',
'cvvCode': '123',
'showBackView': false,
}];
payViaExistingCard(BuildContext context, card) async {
await EasyLoading.show(status: 'Please wait....' );
var expiryArr = card['expiryDate'].split('/');
CreditCard stripeCard = CreditCard(
number: card['cardNumber'],
expMonth: int.parse(expiryArr[0]),
expYear: int.parse(expiryArr[1]),
);
var response = await StripeService.payViaExistingCard(
amount: '2500',
currency: 'USD',
card: stripeCard
);
await EasyLoading.dismiss();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(response.message),
duration: new Duration(milliseconds: 1200),
)
).closed.then((_) {
Navigator.pop(context);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Choose existing card'),
),
body: Container(
padding: EdgeInsets.all(20),
child: ListView.builder(
itemCount: cards.length,
itemBuilder: (BuildContext context, int index) {
var card = cards[index];
return InkWell(
onTap: () {
payViaExistingCard(context, card);
},
child: CreditCardWidget(
cardNumber: card['cardNumber'],
expiryDate: card['expiryDate'],
cardHolderName: card['cardHolderName'],
cvvCode: card['cvvCode'],
showBackView: false,
)
);
}
},
),
),
);
}
}
and this is the error
this is the code for CreditCardWidget
const Map<CardType, String> CardTypeIconAsset = <CardType, String>{
CardType.visa: 'icons/visa.png',
CardType.americanExpress: 'icons/amex.png',
CardType.mastercard: 'icons/mastercard.png',
CardType.discover: 'icons/discover.png',
};
class CreditCardWidget extends StatefulWidget {
const CreditCardWidget(
{Key? key,
required this.cardNumber,
required this.expiryDate,
required this.cardHolderName,
required this.cvvCode,
required this.showBackView,
this.animationDuration = const Duration(milliseconds: 500),
this.height,
this.width,
this.textStyle,
this.cardBgColor = const Color(0xff1b447b),
this.obscureCardNumber = true,
this.obscureCardCvv = true,
this.labelCardHolder = 'CARD HOLDER',
this.labelExpiredDate = 'MM/YY',
this.cardType,
this.isHolderNameVisible = false,
this.backgroundImage,
this.glassmorphismConfig,
this.isChipVisible = true,
this.isSwipeGestureEnabled = true,
this.customCardTypeIcons = const <CustomCardTypeIcon>[],
required this.onCreditCardWidgetChange})
: super(key: key);
final String cardNumber;
final String expiryDate;
final String cardHolderName;
final String cvvCode;
final TextStyle? textStyle;
final Color cardBgColor;
final bool showBackView;
final Duration animationDuration;
final double? height;
final double? width;
final bool obscureCardNumber;
final bool obscureCardCvv;
final void Function(CreditCardBrand) onCreditCardWidgetChange;
final bool isHolderNameVisible;
final String? backgroundImage;
final bool isChipVisible;
final Glassmorphism? glassmorphismConfig;
final bool isSwipeGestureEnabled;
final String labelCardHolder;
final String labelExpiredDate;
final CardType? cardType;
final List<CustomCardTypeIcon> customCardTypeIcons;
#override
_CreditCardWidgetState createState() => _CreditCardWidgetState();
}
class _CreditCardWidgetState extends State<CreditCardWidget>
with SingleTickerProviderStateMixin {
late AnimationController controller;
late Animation<double> _frontRotation;
late Animation<double> _backRotation;
late Gradient backgroundGradientColor;
late bool isFrontVisible = true;
late bool isGestureUpdate = false;
bool isAmex = false;
#override
void initState() {
super.initState();
///initialize the animation controller
controller = AnimationController(
duration: widget.animationDuration,
vsync: this,
);
_gradientSetup();
_updateRotations(false);
}
void _gradientSetup() {
backgroundGradientColor = LinearGradient(
// Where the linear gradient begins and ends
begin: Alignment.topRight,
end: Alignment.bottomLeft,
// Add one stop for each color. Stops should increase from 0 to 1
stops: const <double>[0.1, 0.4, 0.7, 0.9],
colors: <Color>[
widget.cardBgColor.withOpacity(1),
widget.cardBgColor.withOpacity(0.97),
widget.cardBgColor.withOpacity(0.90),
widget.cardBgColor.withOpacity(0.86),
],
);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
///
/// If uer adds CVV then toggle the card from front to back..
/// controller forward starts animation and shows back layout.
/// controller reverse starts animation and shows front layout.
///
if (!isGestureUpdate) {
_updateRotations(false);
if (widget.showBackView) {
controller.forward();
} else {
controller.reverse();
}
} else {
isGestureUpdate = false;
}
final CardType? cardType = widget.cardType != null
? widget.cardType
: detectCCType(widget.cardNumber);
widget.onCreditCardWidgetChange(CreditCardBrand(cardType));
return Stack(
children: <Widget>[
_cardGesture(
child: AnimationCard(
animation: _frontRotation,
child: _buildFrontContainer(),
),
),
_cardGesture(
child: AnimationCard(
animation: _backRotation,
child: _buildBackContainer(),
),
),
],
);
}
void _leftRotation() {
_toggleSide(false);
}
void _rightRotation() {
_toggleSide(true);
}
void _toggleSide(bool isRightTap) {
_updateRotations(!isRightTap);
if (isFrontVisible) {
controller.forward();
isFrontVisible = false;
} else {
controller.reverse();
isFrontVisible = true;
}
}
void _updateRotations(bool isRightSwipe) {
setState(() {
final bool rotateToLeft =
(isFrontVisible && !isRightSwipe) || !isFrontVisible &&
isRightSwipe;
///Initialize the Front to back rotation tween sequence.
_frontRotation = TweenSequence<double>(
<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: Tween<double>(
begin: 0.0, end: rotateToLeft ? (pi / 2) : (-pi / 2))
.chain(CurveTween(curve: Curves.linear)),
weight: 50.0,
),
TweenSequenceItem<double>(
tween: ConstantTween<double>(rotateToLeft ? (-pi / 2) : (pi /
2)),
weight: 50.0,
),
],
).animate(controller);
///Initialize the Back to Front rotation tween sequence.
_backRotation = TweenSequence<double>(
<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: ConstantTween<double>(rotateToLeft ? (pi / 2) : (-pi /
2)),
weight: 50.0,
),
TweenSequenceItem<double>(
tween: Tween<double>(
begin: rotateToLeft ? (-pi / 2) : (pi / 2), end: 0.0)
.chain(
CurveTween(curve: Curves.linear),
),
weight: 50.0,
),
],
).animate(controller);
});
}
///
/// Builds a front container containing
/// Card number, Exp. year and Card holder name
///
Widget _buildFrontContainer() {
final TextStyle defaultTextStyle =
Theme.of(context).textTheme.headline6!.merge(
const TextStyle(
color: Colors.white,
fontFamily: 'halter',
fontSize: 16,
package: 'flutter_credit_card',
),
);
final String number = widget.obscureCardNumber
? widget.cardNumber.replaceAll(RegExp(r'(?<=.{4})\d(?=.{4})'),
'*')
: widget.cardNumber;
return CardBackground(
backgroundImage: widget.backgroundImage,
backgroundGradientColor: backgroundGradientColor,
glassmorphismConfig: widget.glassmorphismConfig,
height: widget.height,
width: widget.width,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
flex: widget.isChipVisible ? 2 : 0,
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
if (widget.isChipVisible)
Padding(
padding: const EdgeInsets.only(left: 16),
child: Image.asset(
'icons/chip.png',
package: 'flutter_credit_card',
scale: 1,
),
),
const Spacer(),
Align(
alignment: Alignment.topRight,
child: Padding(
padding: const EdgeInsets.only(left: 16, right: 16,
top: 8),
child: widget.cardType != null
? getCardTypeImage(widget.cardType)
: getCardTypeIcon(widget.cardNumber),
),
),
],
),
),
const SizedBox(
height: 10,
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 16),
child: Text(
widget.cardNumber.isEmpty ? 'XXXX XXXX XXXX XXXX' :
number,
style: widget.textStyle ?? defaultTextStyle,
),
),
),
Expanded(
flex: 1,
child: Padding(
padding: const EdgeInsets.only(left: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'VALID\nTHRU',
style: widget.textStyle ??
defaultTextStyle.copyWith(fontSize: 7),
textAlign: TextAlign.center,
),
const SizedBox(width: 5),
Text(
widget.expiryDate.isEmpty
? widget.labelExpiredDate
: widget.expiryDate,
style: widget.textStyle ?? defaultTextStyle,
),
],
),
),
),
Visibility(
visible: widget.isHolderNameVisible,
child: Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 16, right: 16,
bottom: 16),
child: Text(
widget.cardHolderName.isEmpty
? widget.labelCardHolder
: widget.cardHolderName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: widget.textStyle ?? defaultTextStyle,
),
),
),
),
],
),
);
}
///
/// Builds a back container containing cvv
///
Widget _buildBackContainer() {
final TextStyle defaultTextStyle =
Theme.of(context).textTheme.headline6!.merge(
const TextStyle(
color: Colors.black,
fontFamily: 'halter',
fontSize: 16,
package: 'flutter_credit_card',
),
);
final String cvv = widget.obscureCardCvv
? widget.cvvCode.replaceAll(RegExp(r'\d'), '*')
: widget.cvvCode;
return CardBackground(
backgroundImage: widget.backgroundImage,
backgroundGradientColor: backgroundGradientColor,
glassmorphismConfig: widget.glassmorphismConfig,
height: widget.height,
width: widget.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
flex: 2,
child: Container(
margin: const EdgeInsets.only(top: 16),
height: 48,
color: Colors.black,
),
),
Expanded(
flex: 2,
child: Container(
margin: const EdgeInsets.only(top: 16),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
flex: 9,
child: Container(
height: 48,
color: Colors.white70,
),
),
Expanded(
flex: 3,
child: Container(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(5),
child: Text(
widget.cvvCode.isEmpty
? isAmex
? 'XXXX'
: 'XXX'
: cvv,
maxLines: 1,
style: widget.textStyle ?? defaultTextStyle,
),
),
),
)
],
),
),
),
Expanded(
flex: 2,
child: Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.only(left: 16, right: 16,
bottom: 16),
child: widget.cardType != null
? getCardTypeImage(widget.cardType)
: getCardTypeIcon(widget.cardNumber),
),
),
),
],
),
);
}
Widget _cardGesture({required Widget child}) {
bool isRightSwipe = true;
return widget.isSwipeGestureEnabled
? GestureDetector(
onPanEnd: (_) {
isGestureUpdate = true;
if (isRightSwipe) {
_leftRotation();
} else {
_rightRotation();
}
},
onPanUpdate: (DragUpdateDetails details) {
// Swiping in right direction.
if (details.delta.dx > 0) {
isRightSwipe = true;
}
// Swiping in left direction.
if (details.delta.dx < 0) {
isRightSwipe = false;
}
},
child: child,
)
: child;
}
/// Credit Card prefix patterns as of March 2019
/// A [List<String>] represents a range.
/// i.e. ['51', '55'] represents the range of cards starting with
'51'
to those starting with '55'
Map<CardType, Set<List<String>>> cardNumPatterns =
<CardType, Set<List<String>>>{
CardType.visa: <List<String>>{
<String>['4'],
},
CardType.americanExpress: <List<String>>{
<String>['34'],
<String>['37'],
},
CardType.discover: <List<String>>{
<String>['6011'],
<String>['622126', '622925'],
<String>['644', '649'],
<String>['65']
},
CardType.mastercard: <List<String>>{
<String>['51', '55'],
<String>['2221', '2229'],
<String>['223', '229'],
<String>['23', '26'],
<String>['270', '271'],
<String>['2720'],
},
};
/// This function determines the Credit Card type based on the
cardPatterns
/// and returns it.
CardType detectCCType(String cardNumber) {
//Default card type is other
CardType cardType = CardType.otherBrand;
if (cardNumber.isEmpty) {
return cardType;
}
cardNumPatterns.forEach(
(CardType type, Set<List<String>> patterns) {
for (List<String> patternRange in patterns) {
// Remove any spaces
String ccPatternStr =
cardNumber.replaceAll(RegExp(r'\s+\b|\b\s'), '');
final int rangeLen = patternRange[0].length;
// Trim the Credit Card number string to match the pattern
prefix length
if (rangeLen < cardNumber.length) {
ccPatternStr = ccPatternStr.substring(0, rangeLen);
}
if (patternRange.length > 1) {
// Convert the prefix range into numbers then make sure the
// Credit Card num is in the pattern range.
// Because Strings don't have '>=' type operators
final int ccPrefixAsInt = int.parse(ccPatternStr);
final int startPatternPrefixAsInt = int.parse(patternRange[0]);
final int endPatternPrefixAsInt = int.parse(patternRange[1]);
if (ccPrefixAsInt >= startPatternPrefixAsInt &&
ccPrefixAsInt <= endPatternPrefixAsInt) {
// Found a match
cardType = type;
break;
}
} else {
// Just compare the single pattern prefix with the Credit
Card prefix
if (ccPatternStr == patternRange[0]) {
// Found a match
cardType = type;
break;
}
}
}
},
);
return cardType;
}
Widget getCardTypeImage(CardType? cardType) {
final List<CustomCardTypeIcon> customCardTypeIcon =
getCustomCardTypeIcon(cardType!);
if(customCardTypeIcon.isNotEmpty){
return customCardTypeIcon.first.cardImage;
} else {
return Image.asset(
CardTypeIconAsset[cardType]!,
height: 48,
width: 48,
package: 'flutter_credit_card',
);
}
}
// This method returns the icon for the visa card type if found
// else will return the empty container
Widget getCardTypeIcon(String cardNumber) {
Widget icon;
final CardType ccType = detectCCType(cardNumber);
final List<CustomCardTypeIcon> customCardTypeIcon =
getCustomCardTypeIcon(ccType);
if (customCardTypeIcon.isNotEmpty) {
icon = customCardTypeIcon.first.cardImage;
isAmex = ccType == CardType.americanExpress;
} else {
switch (ccType) {
case CardType.visa:
icon = Image.asset(
CardTypeIconAsset[ccType]!,
height: 48,
width: 48,
package: 'flutter_credit_card',
);
isAmex = false;
break;
case CardType.americanExpress:
icon = Image.asset(
CardTypeIconAsset[ccType]!,
height: 48,
width: 48,
package: 'flutter_credit_card',
);
isAmex = true;
break;
case CardType.mastercard:
icon = Image.asset(
CardTypeIconAsset[ccType]!,
height: 48,
width: 48,
package: 'flutter_credit_card',
);
isAmex = false;
break;
case CardType.discover:
icon = Image.asset(
CardTypeIconAsset[ccType]!,
height: 48,
width: 48,
package: 'flutter_credit_card',
);
isAmex = false;
break;
default:
icon = Container(
height: 48,
width: 48,
);
isAmex = false;
break;
}
}
return icon;
}
List<CustomCardTypeIcon> getCustomCardTypeIcon(CardType
currentCardType) =>
widget.customCardTypeIcons
.where((CustomCardTypeIcon element) =>
element.cardType == currentCardType)
.toList();
}
class MaskedTextController extends TextEditingController {
MaskedTextController(
{String? text, required this.mask, Map<String, RegExp>?
translator})
: super(text: text) {
this.translator = translator ??
MaskedTextController.getDefaultTranslator();
addListener(() {
final String previous = _lastUpdatedText;
if (beforeChange(previous, this.text)) {
updateText(this.text);
afterChange(previous, this.text);
} else {
updateText(_lastUpdatedText);
}
});
updateText(this.text);
}
String mask;
late Map<String, RegExp> translator;
Function afterChange = (String previous, String next) {};
Function beforeChange = (String previous, String next) {
return true;
};
String _lastUpdatedText = '';
void updateText(String text) {
if (text.isNotEmpty) {
this.text = _applyMask(mask, text);
} else {
this.text = '';
}
_lastUpdatedText = this.text;
}
void updateMask(String mask, {bool moveCursorToEnd = true}) {
this.mask = mask;
updateText(text);
if (moveCursorToEnd) {
this.moveCursorToEnd();
}
}
void moveCursorToEnd() {
final String text = _lastUpdatedText;
selection = TextSelection.fromPosition(TextPosition(offset:
text.length));
}
#override
set text(String newText) {
if (super.text != newText) {
super.text = newText;
moveCursorToEnd();
}
}
static Map<String, RegExp> getDefaultTranslator() {
return <String, RegExp>{
'A': RegExp(r'[A-Za-z]'),
'0': RegExp(r'[0-9]'),
'#': RegExp(r'[A-Za-z0-9]'),
'*': RegExp(r'.*')
};
}
String _applyMask(String? mask, String value) {
String result = '';
int maskCharIndex = 0;
int valueCharIndex = 0;
while (true) {
// if mask is ended, break.
if (maskCharIndex == mask!.length) {
break;
}
// if value is ended, break.
if (valueCharIndex == value.length) {
break;
}
final String maskChar = mask[maskCharIndex];
final String valueChar = value[valueCharIndex];
// value equals mask, just set
if (maskChar == valueChar) {
result += maskChar;
valueCharIndex += 1;
maskCharIndex += 1;
continue;
}
// apply translator if match
if (translator.containsKey(maskChar)) {
if (translator[maskChar]!.hasMatch(valueChar)) {
result += valueChar;
maskCharIndex += 1;
}
valueCharIndex += 1;
continue;
}
// not masked value, fixed char on mask
result += maskChar;
maskCharIndex += 1;
continue;
}
return result;
}
}
enum CardType {
otherBrand,
mastercard,
visa,
americanExpress,
discover,
}
can someone help me to fix this??
thank you very much
The issue is CreditCardWidget has a required parameter onCreditCardWidgetChange which expects a function, it's missing in your code. You should change your code to pass that:
CreditCardWidget(
cardNumber: card['cardNumber'],
expiryDate: card['expiryDate'],
cardHolderName: card['cardHolderName'],
cvvCode: card['cvvCode'],
showBackView: false,
onCreditCardWidgetChange: (brand) {
print(brand);
},
),

Update the state of navigation bar item in flutter

I have Navigation page contain navigation bar, one of these items it's friend request and I want show number of friend request, I do that, but I want update the number of friend request of item when the number increasing or decreasing in navigation page.
I'm passing the values of notifications below, so when I try to add a new friend request the notification numbers not change but when I closed app and reopen again the friend requests updated.
body: Stack(
children: <Widget>[
IndexedStack(
children: <Widget>[
FriendRequestes(),
ChatListScreen(),
HomePage),
Tasks(),
Projects()
],
index: _selectedItem,
)
// Streambuilder to update the number of friend request from firestore
StreamBuilder(
stream: _firestore
.collection(USERS_COLLECTION)
.where(UID_FIELD, isEqualTo: widget.me.uid)
.limit(1)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
const Text('Loading...');
} else {
return ListView.builder(
physics:
NeverScrollableScrollPhysics(),
shrinkWrap: true,
scrollDirection:
Axis.vertical,
itemCount: snapshot
.data.documents.length,
itemBuilder:
(context, index) {
DocumentSnapshot userData = snapshot.data.documents[index];
List<String> requested = <String>[];
if(userData.data[REQUESTED_FIELD] != null)
{
List.from(userData.data[REQUESTED_FIELD]).forEach((element){
requested.add(element);
});
}
widget.me = User(
arrayRequested: requested,
);
rebuild(widget.me);
return Container(width: 0.0,height: 0.0,);
});
}
return Container(
height: 0.0,
width: 0.0,
);
},
),
bottomNavigationBar: CustomBottomNavigationBar(
onChange: (val) {
setState(() {
_selectedItem = val;
});
},
defaultSelectedIndex: 2,
width: width,
lang: widget.myAppLang,
iconList: [
"assets/icon/icon_friendReq.png",
"assets/icon/icon_chats.png",
"assets/icon/icon_home.png",
"assets/icon/icon_tasks.png",
"assets/icon/icon_projects.png",
],
textList: [
"Profile",
"Chats",
"Home",
"Tasks",
"Projects",
],
// here I'm passing notification number
notiList: [widget.me.arrayRequested.length, 0, 0, 0, 0],
),
// to update the state of number of friend requests from firestore
rebuild(User user){
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
widget.me = user;
});
});
}
// Navigation Bar
class CustomBottomNavigationBar extends StatefulWidget {
final int defaultSelectedIndex;
final double width;
String lang;
final Function(int) onChange;
final List<String> iconList;
final List<String> textList;
final List<int> notiList;
CustomBottomNavigationBar(
{this.defaultSelectedIndex = 0,
#required this.iconList,
#required this.textList,
#required this.notiList,
#required this.onChange,
#required this.width,
#required this.lang,
});
#override
_CustomBottomNavigationBarState createState() =>
_CustomBottomNavigationBarState();
}
class _CustomBottomNavigationBarState extends State<CustomBottomNavigationBar> {
int _selectedIndex = 0;
double _width;
String _lang;
List<String> _iconList = [];
List<String> _textList = [];
List<int> _notiList = [];
#override
void initState() {
// TODO: implement initState
super.initState();
_selectedIndex = widget.defaultSelectedIndex;
_iconList = widget.iconList;
_textList = widget.textList;
_notiList = widget.notiList;
_width = widget.width;
_lang = widget.lang;
}
#override
Widget build(BuildContext context) {
List<Widget> _navBarItemList = [];
for (var i = 0; i < _iconList.length; i++) {
_navBarItemList
.add(buildNavBarItem(_width, _lang, _iconList[i], _textList[i], _notiList[i], i));
}
return Row(
children: _navBarItemList,
);
}
Widget buildNavBarItem(
double width, String lang, String icon, String text, int n, int index) {
return GestureDetector(
onTap: () {
setState(() {
widget.onChange(index);
_selectedIndex = index;
n = n;
});
},
child: Stack(
children: [
Container(
height: width * 0.18,
width: MediaQuery.of(context).size.width / _iconList.length,
decoration: index == _selectedIndex
? BoxDecoration(
color: GlobalUniversal.blackForIcons.withOpacity(0.08),
border: Border(
bottom: BorderSide(width: 4, color: GlobalUniversal.purple),
),
/*
gradient: LinearGradient(colors: [
Colors.green.withOpacity(0.3),
Colors.green.withOpacity(0.015),
], begin: Alignment.bottomCenter, end: Alignment.topCenter)
*/
// color: index == _selectedItemIndex ? Colors.green : Colors.white,
)
: BoxDecoration(),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ImageIcon(
AssetImage(icon),
color: index == _selectedIndex
? GlobalUniversal.blackForIcons
: Colors.grey,
),
SizedBox(
height: 3.0,
),
Text(
text,
textScaleFactor: 1.0,
style: TextStyle(
color: index == _selectedIndex
? GlobalUniversal.blackForIcons
: Colors.grey,
fontFamily: lang != LANG_EN ? FONT_AR : FONT_EN),
),
],
),
),
Visibility(
visible: n != 0 && n != null,
child: Container(
margin: EdgeInsets.all(5.0),
padding: EdgeInsets.all(5.0),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Color(0xffc32c37),
),
child: Text(
n.toString(),
textScaleFactor: 1.0,
style: TextStyle(
color: GlobalUniversal.whiteBG,
fontSize: 10.0),
),
),
),
],
),
);
}
}
The state is not updating because the number of the request value is not in a StreamBuilder which means it is not asynchronous.

How to show all dates in a horizontal scroll view

I want to have all the dates of a month in a horizontal scroll view. The current week 7 days should be displayed first and on scrolling right the previous dates should be shown. later on scrolling left the later weeks dates should be displayed an don tap of a date i should get the date in return. How to do this? I have tried using the below. It displays dates and scrolls horizontally as well but it displays only multiple of 7 and all the exact dates of a month. Also on tapping the date it does not return the position form listview builder as 0 and i returns the index.
Widget displaydates(int week) {
return ListView.builder(
itemCount: 5,
itemBuilder: (BuildContext context, int position) {
return Row(
children: <Widget>[
for (int i = 1; i < 8; i++)
Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
onTap: () {
print(position);
},
child: Text(
((week * 7) + i).toString() + " ",
style: TextStyle(),
),
),
),
],
);
});
}
I am calling this like:
displaydates(0),
displaydates(1),
displaydates(2),
displaydates(3),
displaydates(4),
UPDATE:
You can get last day of month using below code :
DateTime(year,month + 1).subtract(Duration(days: 1)).day;
You should use ModalBottomSheet for the same and then pop that on selection with the Navigator.of(context).pop(result);
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return ListView.builder(
itemCount: 5,
itemBuilder: (BuildContext context, int position) {
return Row(
children: <Widget>[
for (int i = 1; i < 8; i++)
Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
onTap: () {
Navigator.of(context).pop(position);
},
child: Text(
((week * 7) + i).toString() + " ",
style: TextStyle(),
),
),
),
],
);
}
);
}
);
I have created a widget that let me select date from a list and it is scrollable(along time ago). There lot of code but you can use the selected date under any widget which parent wrapped from InheritedWidget.
Here is the code(Note that I also created a package for this, if you dont like to write this much code for this):
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class MyInheritedWidget extends InheritedWidget {
final DateTime date;
final int selectedDay;
final int monthDateCount;
final bool isDateHolderActive;
final Map<int, bool> dayAvailabilityMap;
final ValueChanged<bool> toggleDateHolderActive;
final ValueChanged<int> setSelectedDay;
MyInheritedWidget({
Key key,
this.date,
this.selectedDay,
this.monthDateCount,
this.isDateHolderActive,
this.dayAvailabilityMap,
this.toggleDateHolderActive,
this.setSelectedDay,
Widget child,
}) : super(key: key, child: child);
#override
bool updateShouldNotify(MyInheritedWidget oldWidget) {
return oldWidget.selectedDay != selectedDay ||
oldWidget.toggleDateHolderActive != toggleDateHolderActive;
}
}
class DateIndicatorPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
DateIndicator(),
Expanded(
child: Container(),
),
],
),
),
);
}
}
class DateIndicator extends StatefulWidget {
static MyInheritedWidget of(BuildContext context) => context.dependOnInheritedWidgetOfExactType();
#override
_DateIndicatorState createState() => _DateIndicatorState();
}
class _DateIndicatorState extends State<DateIndicator> {
DateTime date = DateTime.now();
int selectedDay = 1;
int monthDateCount = 1;
bool isDateHolderActive = false;
Map<int, bool> dayAvailabilityMap = {};
void toggleDateHolderActive(bool flag) {
setState(() {
isDateHolderActive = flag;
});
}
void setSelectedDay(int index) {
setState(() {
selectedDay = index;
});
}
#override
void initState() {
final DateTime dateForValues = new DateTime(date.year, date.month + 1, 0);
monthDateCount = dateForValues.day;
// Just to show how to activate when something exist for this day(from network response or something)
dayAvailabilityMap[1] = true;
dayAvailabilityMap[2] = true;
dayAvailabilityMap[3] = true;
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
height: 68.0,
padding:
const EdgeInsets.only(left: 7.0, right: 3.0, top: 2.0, bottom: 2.0),
decoration: BoxDecoration(
color: Theme.of(context).secondaryHeaderColor,
boxShadow: [
BoxShadow(
color: Colors.blueAccent.withOpacity(.7),
offset: Offset(0.0, .5),
blurRadius: 3.0,
spreadRadius: 0.3),
],
),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: monthDateCount, // to avoid showing zero
itemBuilder: (BuildContext context, int index) {
return MyInheritedWidget(
date: date,
selectedDay: selectedDay,
monthDateCount: monthDateCount,
isDateHolderActive: isDateHolderActive,
dayAvailabilityMap: dayAvailabilityMap,
toggleDateHolderActive: toggleDateHolderActive,
setSelectedDay: setSelectedDay,
child: DateHolder(index));
}),
);
}
}
class DateHolder extends StatelessWidget {
DateHolder(this.index);
final int index;
final Widget activeBubble = Container(
width: 15.0,
height: 15.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.deepOrangeAccent,
),
);
#override
Widget build(BuildContext context) {
final appState = DateIndicator.of(context);
return InkWell(
onTap: () {
appState.toggleDateHolderActive(true);
appState.setSelectedDay(index);
print("Date ${index} selected!");
},
child: Stack(
children: <Widget>[
Column(
children: <Widget>[
Container(
margin: const EdgeInsets.only(right: 5.0),
child: Text(
"${DateFormat('EEEE').format(DateTime(appState.date.year, appState.date.month, index)).substring(0, 1)}",
style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 12.0),
)),
Container(
width: 45.0,
height: 45.0,
margin: const EdgeInsets.only(right: 5.0),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
border: (index == (appState.selectedDay) &&
appState.isDateHolderActive == true)
? Border.all(width: 2.0, color: Theme.of(context).primaryColor)
: Border.all(color: Colors.transparent),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Text(
"${index + 1}", // to avoid showing zero
style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 16.0),
),
),
),
),
],
),
(appState.dayAvailabilityMap[index] ?? false)
? Positioned(right: 8.0, bottom: 5.0, child: activeBubble)
: Container(),
],
),
);
}
}