import 'package:duration_picker/duration_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:habitrack_app/main.dart'; import 'package:intl/intl.dart'; abstract class SettingEntry extends StatefulWidget { const SettingEntry({required this.name, super.key}); final String name; @override State createState() => _SettingEntryState(); dynamic getValue() { throw UnimplementedError(); } void setValue(dynamic newValue) { throw UnimplementedError(); } } class _SettingEntryState extends State { @override Widget build(BuildContext context) { return const Text('Abstract, not implemented'); } } // ###############################TEXT######################################### class SettingEntryText extends SettingEntry { SettingEntryText({ required super.name, this.defaultValue = 'Some Text', super.key, }) { setValue(defaultValue); } final TextEditingController valueController = TextEditingController(); final String defaultValue; @override State createState() => _SettingEntryTextState(); @override String getValue() { logger.i('GETTING VALUE ${valueController.text}'); return valueController.text; } @override void setValue(dynamic newValue) { if (newValue is! String) { throw Exception('Value of SettingEntryText can only be a String!'); } valueController.text = newValue; } } class _SettingEntryTextState extends State { void monitorTextChange() { final text = widget.valueController.text; logger.i('TEXT: $text'); } @override void initState() { super.initState(); // Start listening to changes. widget.valueController.addListener(monitorTextChange); } @override void dispose() { // Clean up the controller when the widget is removed from the widget tree. // This also removes the _printLatestValue listener. widget.valueController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Column( children: [ Container( alignment: AlignmentDirectional.bottomStart, margin: const EdgeInsets.only(top: 7.5, bottom: 7.5), // color: Colors.black, child: Text( widget.name, textAlign: TextAlign.left, style: Theme.of(context).textTheme.bodyMedium!.copyWith( color: Theme.of(context).colorScheme.onPrimaryContainer, fontWeight: FontWeight.bold, fontSize: 15, ), ), ), TextField( keyboardType: TextInputType.text, controller: widget.valueController, ), ], ); } } // ###############################NUMERIC####################################### class SettingEntryNumeric extends SettingEntry { SettingEntryNumeric({required super.name, int defaultValue = 0, super.key}) { setValue(defaultValue); } final TextEditingController valueController = TextEditingController(); @override State createState() => _SettingEntryNumericState(); @override int getValue() { return int.parse(valueController.text); } @override void setValue(dynamic newValue) { if (newValue is! int) { throw Exception('Value of SettingEntryNumeric can only be an integer!'); } valueController.text = newValue.toString(); } } class _SettingEntryNumericState extends State { @override Widget build(BuildContext context) { return Column( children: [ Text(widget.name), TextField( keyboardType: TextInputType.number, controller: widget.valueController, ), ], ); } } // ###############################SLIDER######################################## class SettingEntrySlider extends SettingEntry { SettingEntrySlider({ required this.topValue, required this.divisions, required super.name, double defaultValue = 0.0, super.key, }) { setValue(defaultValue); } final double topValue; final int divisions; final value = DoubleSaver(); @override State createState() => _SettingEntrySliderState(); @override double getValue() { return value.v; } @override void setValue(dynamic newValue) { if (newValue is! double) { throw Exception('Value of SettingEntrySlider can only be a Double!'); } value.v = newValue; } } class DoubleSaver { double v = 0; } class _SettingEntrySliderState extends State { @override Widget build(BuildContext context) { return Column( children: [ Row( children: [ //Expanded(flex: 2, child: Text(widget.name)), Text(widget.name), Text(' ${widget.getValue()}'), ], ), Row( children: [ const Text('0 '), Expanded( child: Slider( value: widget.getValue(), divisions: widget.divisions, max: widget.topValue, onChanged: sliderChange, ), ), Text(' ${widget.topValue}'), ], ), ], ); } void sliderChange(double newValue) { setState(() { widget.setValue(newValue); }); } } // #############################DURATION####################################### class IntSaver { IntSaver({this.v = 30}); int v; } class SettingEntryDuration extends SettingEntry { SettingEntryDuration({ required super.name, required this.defaultValue, super.key, }) { val = IntSaver(v: defaultValue); } final int defaultValue; late final IntSaver val; @override State createState() => _SettingEntryDurationState(); @override int getValue() { return val.v; } @override void setValue(dynamic newValue) { if (newValue is! int) { throw Exception('Value of SettingEntryDuration can only be a Double!'); } val.v = newValue; } } class _SettingEntryDurationState extends State { @override Widget build(BuildContext context) { final textCurrent = AppLocalizations.of(context)!.widgetSettings_durationPickerCurrent; return Column( children: [ Container( alignment: AlignmentDirectional.bottomStart, margin: const EdgeInsets.only(top: 7.5, bottom: 7.5), child: Text( '$textCurrent ${widget.getValue()}m', style: Theme.of(context).textTheme.bodyMedium!.copyWith( color: Theme.of(context).colorScheme.onPrimaryContainer, fontWeight: FontWeight.bold, fontSize: 15, ), ), ), OutlinedButton( style: OutlinedButton.styleFrom( backgroundColor: Theme.of(context).colorScheme.primary, ), onPressed: () async { var resultingDuration = await showDurationPicker( context: context, initialTime: Duration(minutes: widget.val.v), ); resultingDuration ??= Duration(minutes: widget.defaultValue); // ignore: use_build_context_synchronously ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Chose duration: ${resultingDuration.inMinutes}'), ), ); widget.setValue(resultingDuration.inMinutes); setState(() {}); }, child: Text( AppLocalizations.of(context)!.widgetSettings_durationPickerButton, style: Theme.of(context).textTheme.bodyMedium!.copyWith( color: Theme.of(context).colorScheme.onPrimary, ), ), ), ], ); } } // ###############################DATE######################################### class DateSaver { DateTime v = DateTime.now(); } class SettingEntryDate extends SettingEntry { SettingEntryDate({required super.name, DateTime? defaultValue, super.key}) { this.defaultValue = defaultValue ?? DateTime.now(); setValue(defaultValue); } final date = DateSaver(); late final DateTime defaultValue; @override State createState() => _SettingEntryDateState(); @override DateTime getValue() { return date.v; } @override void setValue(dynamic newValue) { if (newValue is! DateTime) { throw Exception('Value of SettingEntryNumeric can only be a DateTime!'); } date.v = newValue; } } class _SettingEntryDateState extends State { @override Widget build(BuildContext context) { final dateFormat = DateFormat('dd. MMMM'); return Column( children: [ Text( '${widget.name}: ${dateFormat.format(widget.date.v)} ', style: Theme.of(context).textTheme.bodyMedium!.copyWith( color: Theme.of(context).colorScheme.primaryContainer, ), ), TextButton( onPressed: datePicker, child: const Text('Choose a date'), ), ], ); } Future datePicker() async { final pickedDate = await showDatePicker( context: context, initialDate: widget.date.v, //get today's date firstDate: DateTime.now(), //DateTime.now() - not to allow to choose before today. lastDate: DateTime(2101), ); final cleanDate = pickedDate ?? widget.date.v; widget.setValue(cleanDate); setState(() {}); } }