376 lines
9.5 KiB
Dart
376 lines
9.5 KiB
Dart
![]() |
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<StatefulWidget> createState() => _SettingEntryState();
|
||
|
|
||
|
dynamic getValue() {
|
||
|
throw UnimplementedError();
|
||
|
}
|
||
|
|
||
|
void setValue(dynamic newValue) {
|
||
|
throw UnimplementedError();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class _SettingEntryState extends State<SettingEntry> {
|
||
|
@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<SettingEntryText> 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<SettingEntryText> {
|
||
|
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<SettingEntryNumeric> 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<SettingEntryNumeric> {
|
||
|
@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<SettingEntrySlider> 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<SettingEntrySlider> {
|
||
|
@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<SettingEntryDuration> 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<SettingEntryDuration> {
|
||
|
@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<SettingEntryDate> 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<SettingEntryDate> {
|
||
|
@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<void> 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(() {});
|
||
|
}
|
||
|
}
|