Created
June 11, 2022 20:21
-
-
Save antiv/339823f661840a47f9fcb79cc05ad37e to your computer and use it in GitHub Desktop.
Cupertino like DateTime picker with secunds
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
import 'package:flutter/cupertino.dart'; | |
import 'package:intl/intl.dart'; | |
void main() { | |
runApp(MyApp()); | |
} | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
theme: ThemeData.light(), | |
debugShowCheckedModeBanner: false, | |
home: Scaffold( | |
body: Center( | |
child: MyWidget(), | |
), | |
), | |
); | |
} | |
} | |
class MyWidget extends StatefulWidget { | |
@override | |
State<MyWidget> createState() => _MyWidgetState(); | |
} | |
class _MyWidgetState extends State<MyWidget> { | |
final DateFormat _formatter = DateFormat('dd.MM.yyyy HH:mm:ss'); | |
final TextEditingController _controller = TextEditingController(); | |
@override | |
Widget build(BuildContext context) { | |
return Center( | |
child: SizedBox( | |
width: 500, | |
child: Column( | |
mainAxisSize: MainAxisSize.max, | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: [ | |
TextFormField( | |
controller: _controller, | |
textInputAction: TextInputAction.next, | |
keyboardType: TextInputType.datetime, | |
decoration: InputDecoration( | |
labelText: 'Insert date or select clock icon', | |
hintText: 'Insert date', | |
prefixIcon: const Icon(Icons.calendar_today), | |
suffixIcon: InkWell( | |
onTap: () { | |
showDialog( | |
context: context, | |
builder: (BuildContext context) { | |
return _buildAlertDialog(context, _controller); | |
}); | |
}, | |
child: const Icon(Icons.access_time_outlined)), | |
), | |
onChanged: (val) { | |
// _referencedDocumentNumberController.text = val; | |
}, | |
), | |
const SizedBox(height: 5.0), | |
const Text('Cupertino like Date Time picker with secunds!') | |
], | |
), | |
), | |
); | |
} | |
AlertDialog _buildAlertDialog(BuildContext context, controller) { | |
return AlertDialog( | |
// contentPadding: EdgeInsets.zero, | |
insetPadding: EdgeInsets.zero, | |
content: SizedBox( | |
width: 500, | |
height: MediaQuery.of(context).size.height * 0.7, | |
child: TimeSelector( | |
currentTime: DateTime.now(), | |
labelTime: "Select date", | |
labelDate: 'Select time', | |
dmyLabels: const ['Day', 'Month', 'Year'], | |
hmsLabels: const ['Hour', 'Minute', 'Second'], | |
onConfirm: (date) { | |
setState(() { | |
controller.text = _formatter.format(date); | |
}); | |
Navigator.of(context).pop(date); | |
}, | |
onClose: () => Navigator.of(context).pop(), | |
), | |
), | |
); | |
} | |
} | |
class TimeSelector extends StatefulWidget { | |
const TimeSelector({ | |
Key? key, | |
this.labelTime, | |
this.labelDate, | |
this.dmyLabels, | |
this.hmsLabels, | |
this.currentTime, | |
this.onClose, | |
this.onConfirm, | |
}) : super(key: key); | |
final String? labelTime; | |
final String? labelDate; | |
final List<String>? dmyLabels; | |
final List<String>? hmsLabels; | |
final DateTime? currentTime; | |
final void Function()? onClose; | |
final void Function(DateTime date)? onConfirm; | |
@override | |
State<TimeSelector> createState() => _TimeSelectorState(); | |
static Future<DateTime?> showFullScreenSelector({ | |
required BuildContext context, | |
DateTime? currentTime, | |
String? labelTime, | |
String? labelDate, | |
List<String>? dmyLabels, | |
List<String>? hmsLabels, | |
}) { | |
return showGeneralDialog<DateTime?>( | |
context: context, | |
barrierDismissible: false, | |
transitionDuration: const Duration(milliseconds: 150), | |
transitionBuilder: (context, animation, secondaryAnimation, child) { | |
return FadeTransition(opacity: animation, child: child); | |
}, | |
pageBuilder: (context, animation, secondaryAnimation) { | |
return TimeSelector( | |
currentTime: currentTime, | |
labelDate: labelDate, | |
labelTime: labelTime, | |
dmyLabels: dmyLabels, | |
hmsLabels: hmsLabels, | |
onConfirm: (date) => Navigator.of(context).pop(date), | |
onClose: () => Navigator.of(context).pop(), | |
); | |
}, | |
); | |
} | |
} | |
class _TimeSelectorState extends State<TimeSelector> { | |
late DateTime currentTime = widget.currentTime ?? DateTime.now(); | |
bool _dateSelected = false; | |
double fontSize = 42; | |
double itemExtent = 48; | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
backgroundColor: Colors.white, | |
body: SafeArea( | |
child: Center( | |
child: Padding( | |
padding: EdgeInsets.zero, | |
child: LayoutBuilder( | |
builder: (BuildContext context, BoxConstraints constraints) { | |
return Column( | |
crossAxisAlignment: CrossAxisAlignment.center, | |
children: [ | |
Padding( | |
padding: const EdgeInsets.symmetric(horizontal: 15.0), | |
child: Text(_dateSelected ? widget.labelTime ?? '' : widget.labelDate ?? '', | |
style: const TextStyle(fontSize: 24)), | |
), | |
Expanded( | |
child: CupertinoTheme( | |
data: CupertinoThemeData( | |
brightness: Brightness.light, | |
textTheme: CupertinoTextThemeData( | |
pickerTextStyle: Theme.of(context) | |
.textTheme | |
.headline5 | |
?.copyWith( | |
fontSize: fontSize, | |
color: Theme.of(context).primaryColor), | |
), | |
), | |
child: AnimatedSwitcher( | |
duration: const Duration(milliseconds: 400), | |
switchInCurve: Curves.easeIn, | |
switchOutCurve: Curves.easeOut, | |
child: _dateSelected | |
? Row( | |
key: const ValueKey('time'), | |
crossAxisAlignment: CrossAxisAlignment.center, | |
mainAxisSize: MainAxisSize.max, | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: [ | |
...[for (var i = 0; i <= 2; i++) i] | |
.map( | |
(w) => SizedBox( | |
height: 350, | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.center, | |
mainAxisSize: MainAxisSize.max, | |
crossAxisAlignment: CrossAxisAlignment.center, | |
children: [ | |
Text(widget.hmsLabels![w]), | |
SizedBox( | |
height: 300, | |
width: constraints.maxWidth / 3, | |
child: Center( | |
child: CupertinoPicker( | |
// backgroundColor: Colors.white, | |
itemExtent: itemExtent, | |
useMagnifier: true, | |
magnification: 1.2, | |
looping: true, | |
scrollController: | |
FixedExtentScrollController( | |
initialItem: | |
_getInitialItem(w)), | |
children: [ | |
for (var i = 0; | |
i <= (w == 0 ? 23 : 59); | |
i++) | |
i | |
] | |
.map((e) => SizedBox( | |
height: itemExtent, | |
child: e < 10 | |
? Text('0$e') | |
: Text('$e'))) | |
.toList(), | |
onSelectedItemChanged: (value) { | |
currentTime = | |
currentTime.toLocal(); | |
setState(() { | |
currentTime = | |
_setSelectedTime( | |
w, value); | |
}); | |
}, | |
), | |
), | |
), | |
], | |
), | |
), | |
) | |
.toList(), | |
], | |
) | |
: Row( | |
key: const ValueKey('date'), | |
crossAxisAlignment: CrossAxisAlignment.center, | |
mainAxisSize: MainAxisSize.max, | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: [ | |
...[for (var i = 0; i <= 2; i++) i] | |
.map( | |
(w) => SizedBox( | |
height: 350, | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.center, | |
mainAxisSize: MainAxisSize.max, | |
crossAxisAlignment: CrossAxisAlignment.center, | |
children: [ | |
Text(widget.dmyLabels![w]), | |
SizedBox( | |
height: 300, | |
width: w == 2 | |
? constraints.maxWidth / 4 * 2 | |
: constraints.maxWidth / 4, | |
child: Center( | |
child: CupertinoPicker( | |
// backgroundColor: Colors.white, | |
itemExtent: itemExtent, | |
useMagnifier: true, | |
magnification: 1.2, | |
looping: true, | |
scrollController: | |
FixedExtentScrollController( | |
initialItem: | |
_getInitialItem(w, | |
date: true)), | |
children: [ | |
for (var i = 1; | |
i <= | |
(w == 0 | |
? 31 | |
: w == 1 | |
? 12 | |
: 5); | |
i++) | |
i | |
] | |
.map((e) => SizedBox( | |
height: itemExtent, | |
child: w == 2 | |
? Text( | |
'${2017 + e}') | |
: e < 10 | |
? Text('0$e') | |
: Text('$e'))) | |
.toList(), | |
onSelectedItemChanged: (value) { | |
currentTime = | |
currentTime.toLocal(); | |
setState(() { | |
currentTime = | |
_setSelectedDate( | |
w, value); | |
}); | |
}, | |
), | |
), | |
), | |
], | |
), | |
), | |
) | |
.toList(), | |
], | |
), | |
), | |
), | |
), | |
Text(DateFormat('dd.MM.yyyy HH:mm:ss').format(currentTime), | |
style: Theme.of(context) | |
.textTheme | |
.headline5 | |
?.copyWith( | |
color: Theme.of(context).primaryColor),), | |
Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: ActionButtons( | |
setData: () { | |
if (_dateSelected == true) { | |
widget.onConfirm?.call(currentTime); | |
} else { | |
setState(() { | |
_dateSelected = true; | |
}); | |
} | |
}, | |
cleanForm: () => Navigator.of(context).pop(), | |
), | |
) | |
], | |
); | |
}), | |
), | |
), | |
), | |
); | |
} | |
_getInitialItem(int w, {bool? date}) { | |
if (date == true) { | |
if (w == 0) return currentTime.day - 1; | |
if (w == 1) return currentTime.month - 1; | |
if (w == 2) return currentTime.year - 2018; | |
} else { | |
if (w == 0) return currentTime.hour; | |
if (w == 1) return currentTime.minute; | |
if (w == 2) return currentTime.second; | |
} | |
} | |
_setSelectedTime(int w, int value) { | |
return DateTime( | |
currentTime.year, | |
currentTime.month, | |
currentTime.day, | |
w == 0 ? value : currentTime.hour, | |
w == 1 ? value : currentTime.minute, | |
w == 2 ? value : currentTime.second, | |
currentTime.millisecond, | |
currentTime.microsecond); | |
} | |
_setSelectedDate(int w, int value) { | |
return DateTime( | |
w == 2 ? value + 2018 : currentTime.year, | |
w == 1 ? value + 1 : currentTime.month, | |
w == 0 ? value + 1 : currentTime.day, | |
currentTime.hour, | |
currentTime.minute, | |
currentTime.second, | |
currentTime.millisecond, | |
currentTime.microsecond); | |
} | |
} | |
class ActionButtons extends StatelessWidget { | |
const ActionButtons({Key? key, this.setData, this.cleanForm, this.okText, this.cancelText}) : super(key: key); | |
final String? okText; | |
final Function? setData; | |
final String? cancelText; | |
final Function? cleanForm; | |
@override | |
Widget build(BuildContext context) { | |
return LayoutBuilder( | |
builder: (BuildContext context, BoxConstraints constraints) { | |
return Row( | |
mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
children: [ | |
ElevatedButton.icon( | |
label: Text(okText ?? "OK"), | |
onPressed: () => {if (setData != null) setData!()}, | |
icon: const Icon(Icons.check), | |
style: ButtonStyle( | |
fixedSize: MaterialStateProperty.all( | |
Size(constraints.maxWidth / 2 - 5, 30)), | |
), | |
), | |
ElevatedButton.icon( | |
label: Text(cancelText ?? 'Cancel'), | |
onPressed: () => { | |
if (cleanForm != null) cleanForm!() | |
}, | |
icon: const Icon(Icons.clear), | |
style: ButtonStyle( | |
fixedSize: MaterialStateProperty.all( | |
Size(constraints.maxWidth / 2 - 5, 30)), | |
), | |
), | |
], | |
); | |
} | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment