Initial (redacted) commit.

This commit is contained in:
mustard 2024-08-26 00:34:20 +02:00
commit 655f8a036a
368 changed files with 20949 additions and 0 deletions

View file

@ -0,0 +1,465 @@
//import 'dart:ffi';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:habitrack_app/function_widgets/widget_settings_menu/setting_entry.dart';
import 'package:habitrack_app/function_widgets/widget_settings_menu/widget_settings.dart';
import 'package:habitrack_app/infrastructure/widget_wall/items_controller.dart';
import 'package:habitrack_app/infrastructure/widget_wall/items_state.dart';
import 'package:habitrack_app/main.dart';
import 'package:habitrack_app/sembast/timer.dart';
import 'package:logger/logger.dart';
// ignore: must_be_immutable
class CompoundWidgetTimer extends ConsumerStatefulWidget {
CompoundWidgetTimer({required this.item, super.key});
TimerItem item;
double getProgress() {
return (item.current / item.goal) / 60;
}
@override
ConsumerState<CompoundWidgetTimer> createState() =>
_CompoundWidgetTimerState();
}
class _CompoundWidgetTimerState extends ConsumerState<CompoundWidgetTimer>
with WidgetsBindingObserver {
_CompoundWidgetTimerState();
String button1text = '';
Timer _timer = Timer(Duration.zero, () => ());
DateTime calcDue() {
final now = DateTime.now();
const secondsToAdd = 10;
const duration = Duration(seconds: secondsToAdd);
final futureTime = now.add(duration);
Logger().i(futureTime);
return DateTime.now();
}
void handleButton1() {
//start timer from 0
const oneSec = Duration(seconds: 1);
if (widget.item.state == 'initial') {
widget.item = widget.item.copyWith(state: 'running');
ref.watch(homeControllerProvider).edit(widget.item);
_timer = Timer.periodic(
oneSec,
(Timer funcTimer) {
if (widget.item.current == (widget.item.goal * 60)) {
setState(() {
final now = DateTime.now();
widget.item = widget.item.copyWith(
state: 'completed',
completedOn: now.toString(),
);
ref.watch(homeControllerProvider).edit(widget.item);
funcTimer.cancel();
});
} else {
setState(() {
widget.item =
widget.item.copyWith(current: widget.item.current + 1);
ref.watch(homeControllerProvider).edit(widget.item);
});
}
},
);
}
// continue timer
else if (widget.item.state == 'paused') {
widget.item = widget.item.copyWith(state: 'running');
ref.watch(homeControllerProvider).edit(widget.item);
_timer = Timer.periodic(
const Duration(seconds: 1),
(Timer funcTimer) {
if (widget.item.current == (widget.item.goal * 60)) {
setState(() {
final now = DateTime.now();
widget.item = widget.item.copyWith(
state: 'completed',
completedOn: now.toString(),
);
ref.watch(homeControllerProvider).edit(widget.item);
funcTimer.cancel();
});
} else {
setState(() {
widget.item =
widget.item.copyWith(current: widget.item.current + 1);
ref.watch(homeControllerProvider).edit(widget.item);
});
}
},
);
} else if (widget.item.state == 'running') {
widget.item = widget.item.copyWith(state: 'paused');
ref.watch(homeControllerProvider).edit(widget.item);
_timer.cancel();
} else if (widget.item.state == 'completed') {}
}
void stopTimer() {
setState(() {
if (_timer.isActive) {
_timer.cancel();
}
widget.item = widget.item.copyWith(
current: 0,
state: 'initial',
);
ref.watch(homeControllerProvider).edit(widget.item);
});
}
String _formatCurrent() {
final hours = (widget.item.current / 3600).floor();
final minutes = ((widget.item.current - (hours * 3600)) / 60).floor();
return '$hours hours : $minutes minutes';
}
String _formattedTime() {
final minutesTotal = widget.item.goal;
final hours = (minutesTotal / 60).floor();
final minutes = minutesTotal - (hours * 60);
return '$hours hours : $minutes minutes';
}
void _toggleExpansion() {
setState(() {
widget.item = widget.item.copyWith(isExpanded: !widget.item.isExpanded);
ref.watch(homeControllerProvider).edit(widget.item);
});
}
String _getButton1Text(BuildContext context) {
if (widget.item.state == 'initial') {
return AppLocalizations.of(context)!.timerWidget_buttonStart;
} else if (widget.item.state == 'running') {
return AppLocalizations.of(context)!.timerWidget_buttonPause;
} else if (widget.item.state == 'paused') {
return AppLocalizations.of(context)!.timerWidget_buttonContinue;
}
return 'Done';
}
@override
Widget build(BuildContext context) {
ref
..watch(homeControllerProvider)
..watch(itemsProvider);
return Container(
margin: const EdgeInsets.only(left: 10, top: 10, right: 10, bottom: 10),
padding: const EdgeInsets.only(left: 10, top: 15, right: 10, bottom: 15),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
borderRadius: const BorderRadius.all(Radius.circular(7)),
boxShadow: [
BoxShadow(
color: const Color(0x00000000).withOpacity(0.25),
spreadRadius: 2,
blurRadius: 5,
// changes position of shadow
),
],
),
width: double.infinity,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (!widget.item.isExpanded) ...[
Icon(
Icons.timer,
size: 20,
color: Theme.of(context).colorScheme.onPrimary,
),
Expanded(
flex: 2,
child: Container(
margin: const EdgeInsets.only(left: 10, right: 10),
height: 30,
child: ClipRRect(
borderRadius: const BorderRadius.all(
Radius.circular(5),
), // Rounded corners
child: LinearProgressIndicator(
value:
widget.getProgress(), // Progress value (0.0 - 1.0)
backgroundColor:
Colors.grey.withOpacity(0.5), // Background color
valueColor: const AlwaysStoppedAnimation<Color>(
Color(0xffA4E8FD),
), // Progress color
),
),
),
),
Expanded(
child: Text(
widget.item.name,
style: TextStyle(
color: Theme.of(context).colorScheme.onPrimary,
// alignment: TextAlign.left,
),
),
),
],
if (widget.item.isExpanded) ...[
Expanded(
child: Text(
widget.item.name,
textScaler: const TextScaler.linear(2),
style: TextStyle(
color: Theme.of(context).colorScheme.onPrimary,
// alignment: TextAlign.left,
),
),
),
Expanded(
flex: 0,
child: IconButton(
icon: Icon(
Icons.settings,
color: Theme.of(context).colorScheme.onPrimary,
),
onPressed: () => _showSettingsMenu(ref),
),
),
],
Expanded(
flex: 0,
child: IconButton(
icon: Icon(
widget.item.isExpanded
? Icons.arrow_drop_up_outlined
: Icons.arrow_drop_down_circle_outlined,
color: Theme.of(context).colorScheme.onPrimary,
),
onPressed: _toggleExpansion,
),
),
],
),
if (widget.item.isExpanded) ...[
// Additional child elements when expanded
SizedBox(
height: 300,
width: MediaQuery.of(context).size.width,
child: Column(
children: [
Stack(
children: <Widget>[
Container(
alignment: Alignment.center,
// color: Colors.blueAccent,
width: MediaQuery.of(context).size.width,
height: 250,
child: SizedBox(
height: 150,
// color: Colors.greenAccent,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
// ignore: lines_longer_than_80_chars
'${AppLocalizations.of(context)!.timerWidget_current}: ${_formatCurrent()} \n ${AppLocalizations.of(context)!.timerWidget_goal}: ${_formattedTime()}',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color: Theme.of(context)
.colorScheme
.onPrimary,
),
),
],
),
),
),
Positioned(
top: 10,
left: MediaQuery.of(context).size.width * 0.15,
width: MediaQuery.of(context).size.width * 0.60,
height: 220,
child: CircularProgressIndicator(
color: Theme.of(context).colorScheme.onPrimary,
strokeWidth: 7,
value: widget.getProgress(),
semanticsLabel: 'Circular progress indicator',
),
),
],
),
Expanded(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
onPressed: handleButton1,
child: Text(
_getButton1Text(context),
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color: Theme.of(context)
.colorScheme
.onPrimary,
),
),
),
TextButton(
onPressed: stopTimer,
child: Text(
AppLocalizations.of(context)!
.timerWidget_buttonReset,
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color: Theme.of(context)
.colorScheme
.onPrimary,
),
),
),
],
),
],
),
),
],
),
),
// Add more widgets here as needed
],
],
),
);
}
Future<void> _showSettingsMenu(WidgetRef ref) async {
logger.i('Opening settings');
final settingEntries = WidgetSettingsData(
entries: {
'name': SettingEntryText(
name: AppLocalizations.of(context)!.widgetSettings_name,
defaultValue: widget.item.name,
),
'duration': SettingEntryDuration(
name: AppLocalizations.of(context)!.timerWidgetSettings_duration,
defaultValue: widget.item.goal,
),
},
);
if (widget.item.state == 'running') {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
AppLocalizations.of(context)!.timerWidget_pausedForEdit,
),
),
);
} else {
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
content: WidgetSettings(
entries: settingEntries,
),
actions: [
OutlinedButton(
style: OutlinedButton.styleFrom(
backgroundColor: Colors.red,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed: () async {
ref.watch(itemsProvider);
final items = ref.watch(homeControllerProvider);
widget.item = widget.item.copyWith(isVisible: false);
setState(() {
items.edit(widget.item);
});
logger.i('Attempting delete');
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
},
child: Text(
AppLocalizations.of(context)!.widgetSettings_deleteButton,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
fontWeight: FontWeight.bold,
),
),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
backgroundColor: Theme.of(context).colorScheme.onPrimary,
),
onPressed: () {
_timer.cancel();
ref.watch(homeControllerProvider);
logger.i('Attempting edit of name and/or duration');
final name = settingEntries.getValue('name') as String;
logger.i('New name: $name');
final duration = settingEntries.getValue('duration') as int;
widget.item =
widget.item.copyWith(goal: duration, name: name);
setState(() {
ref.watch(homeControllerProvider).edit(widget.item);
});
logger.i('NAME AND DURATION SUCCESSFULLY UPDATED');
Navigator.of(context).pop();
},
child: Text(
AppLocalizations.of(context)!.widgetSettings_saveButton,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.bold,
),
),
),
],
);
},
);
}
}
}

View file

@ -0,0 +1,968 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:habitrack_app/function_widgets/widget_settings_menu/setting_entry.dart';
import 'package:habitrack_app/function_widgets/widget_settings_menu/widget_settings.dart';
import 'package:habitrack_app/infrastructure/widget_wall/items_controller.dart';
import 'package:habitrack_app/infrastructure/widget_wall/items_state.dart';
import 'package:habitrack_app/main.dart';
import 'package:habitrack_app/sembast/tasks_list.dart';
import 'package:intl/intl.dart';
const seperator = '_SEPARATOR_';
// ignore: must_be_immutable
class IndividualTodoWidget extends ConsumerStatefulWidget {
IndividualTodoWidget({
required this.todo,
required this.due,
required this.completed,
required this.item,
required this.isVisible,
required this.createdOn,
required this.completedOn,
super.key,
});
late String todo;
late bool completed;
late DateTime due;
late DateTime createdOn;
late DateTime completedOn;
late TasksItem item;
late bool isVisible;
@override
ConsumerState<IndividualTodoWidget> createState() =>
_IndividualTodoWidgetState();
String stringRepr() {
return [
todo,
due.toString().substring(0, 10),
createdOn,
completedOn,
completed,
isVisible,
].join(seperator);
}
void deleteItem(WidgetRef ref) {
final searchTerm = stringRepr();
if (completed == true) {
final completedTaskList = <String>[...item.completedTaskList];
//search in completed tasks list
for (var i = 0; i < completedTaskList.length; i++) {
if (searchTerm == completedTaskList.elementAt(i)) {
var newItem = searchTerm;
final toConvert = newItem.split(seperator);
final todo = toConvert.elementAtOrNull(0)!;
final due = DateTime.parse(toConvert.elementAtOrNull(1)!);
final createdOn = DateTime.parse(toConvert.elementAtOrNull(2)!);
final completedOn = DateTime.parse(toConvert.elementAtOrNull(3)!);
final completed =
toConvert.elementAtOrNull(4)!.toLowerCase() == 'true';
final dateFormat = DateFormat('yyyy-MM-dd');
final formattedDate = dateFormat.format(due);
newItem = [
todo,
formattedDate.substring(0, 10),
createdOn,
completedOn,
completed,
false,
].join(seperator);
completedTaskList[i] = newItem;
final controller = ref.watch(homeControllerProvider);
item = item.copyWith(completedTaskList: completedTaskList);
controller.edit(item);
}
}
} else {
final taskList = <String>[...item.taskList];
for (var i = 0; i < taskList.length; i++) {
if (searchTerm == taskList.elementAt(i)) {
var newItem = searchTerm;
final toConvert = newItem.split(seperator);
final todo = toConvert.elementAtOrNull(0)!;
final due = DateTime.parse(toConvert.elementAtOrNull(1)!);
final createdOn = DateTime.parse(toConvert.elementAtOrNull(2)!);
final completedOn = DateTime.parse(toConvert.elementAtOrNull(3)!);
final completed =
toConvert.elementAtOrNull(4)!.toLowerCase() == 'true';
final dateFormat = DateFormat('yyyy-MM-dd');
final formattedDate = dateFormat.format(due);
newItem = [
todo,
formattedDate,
createdOn,
completedOn,
completed,
false,
].join(seperator);
taskList[i] = newItem;
final controller = ref.watch(homeControllerProvider);
item = item.copyWith(taskList: taskList);
controller.edit(item);
} else {}
}
}
}
bool overdue() {
final now = DateTime.now();
if (DateTime(now.year, now.month, now.day).isAfter(due)) {
return true;
}
return false;
}
}
class _IndividualTodoWidgetState extends ConsumerState<IndividualTodoWidget> {
_IndividualTodoWidgetState();
void _toggleChecked(bool? param) {
if (widget.overdue() && !widget.completed) {
} else {
final searchTerm = widget.stringRepr();
final completedTaskList = <String>[...widget.item.completedTaskList];
final taskList = <String>[...widget.item.taskList];
if (widget.completed == true) {
//search in completed tasks list
for (var i = 0; i < completedTaskList.length; i++) {
if (searchTerm == completedTaskList.elementAt(i)) {
widget.completed = !widget.completed;
final replacementStr = widget.stringRepr();
completedTaskList.removeAt(i);
taskList.add(replacementStr);
final controller = ref.watch(homeControllerProvider);
widget.item = widget.item.copyWith(
taskList: taskList,
completedTaskList: completedTaskList,
);
setState(() {
controller.edit(widget.item);
});
}
}
} else {
for (var i = 0; i < taskList.length; i++) {
if (searchTerm == taskList.elementAt(i)) {
widget.completed = !widget.completed;
// ignore: cascade_invocations
widget.completedOn = DateTime.now();
final replacementStr = widget.stringRepr();
taskList.removeAt(i);
completedTaskList.add(replacementStr);
final controller = ref.watch(homeControllerProvider);
widget.item = widget.item.copyWith(
taskList: taskList,
completedTaskList: completedTaskList,
);
setState(
() {
controller.edit(widget.item);
},
);
} else {}
}
}
setState(() {});
}
}
void _editItem(String oldItem, String itemToAdd) {
final completedTaskList = <String>[...widget.item.completedTaskList];
final taskList = <String>[...widget.item.taskList];
if (widget.completed == true) {
//search in completed tasks list
for (var i = 0; i < completedTaskList.length; i++) {
if (oldItem == completedTaskList.elementAt(i)) {
completedTaskList.replaceRange(i, i + 1, [itemToAdd]);
final controller = ref.watch(homeControllerProvider);
widget.item =
widget.item.copyWith(completedTaskList: completedTaskList);
setState(() {
controller.edit(widget.item);
});
}
}
} else {
for (var i = 0; i < taskList.length; i++) {
if (oldItem == taskList.elementAt(i)) {
taskList.replaceRange(i, i + 1, [itemToAdd]);
final controller = ref.watch(homeControllerProvider);
widget.item = widget.item.copyWith(taskList: taskList);
setState(
() {
controller.edit(widget.item);
},
);
} else {}
}
}
}
Future<void> _editPopup(String oldItem) async {
final todoFieldEditController = TextEditingController(text: widget.todo);
DateTime? date = widget.due;
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
title: Text(
AppLocalizations.of(context)!.tasksWidget_editTask,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimaryContainer,
fontWeight: FontWeight.bold,
fontSize: 20,
),
),
scrollable: true,
content: Column(
children: <Widget>[
TextField(
controller: todoFieldEditController,
decoration: InputDecoration(hintText: widget.todo),
autofillHints: null,
),
TextButton(
child: Text(
AppLocalizations.of(context)!.taskSettings_duePicker,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
),
onPressed: () async {
final pickedDate = await showDatePicker(
context: context,
initialDate: widget.due,
firstDate: DateTime(2024, 0, 0),
lastDate: DateTime(2101),
);
date = pickedDate;
},
),
],
),
actions: <Widget>[
OutlinedButton(
style: OutlinedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
AppLocalizations.of(context)!.widgetSettings_cancelButton,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed: () {
date ??= widget.due;
if (todoFieldEditController.text.isEmpty) {
todoFieldEditController.text = widget.todo;
}
Navigator.of(context).pop();
final dateFormat = DateFormat('yyyy-MM-dd');
final formattedDate = dateFormat.format(date!);
final newItem = [
todoFieldEditController.text,
formattedDate,
widget.createdOn,
widget.completedOn,
widget.completed,
widget.isVisible,
].join(seperator);
_editItem(oldItem, newItem);
todoFieldEditController.clear();
},
child: Text(
AppLocalizations.of(context)!.widgetSettings_saveButton,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
final df = DateFormat('dd.MM.yyyy');
var dateText =
// ignore: lines_longer_than_80_chars
'${AppLocalizations.of(context)!.taskSettings_due}: ${df.format(widget.due)}';
var backgroundColor = Theme.of(context).colorScheme.secondary;
if (widget.overdue() && !widget.completed) {
dateText = AppLocalizations.of(context)!.tasksWidget_overdue;
backgroundColor = Theme.of(context).colorScheme.tertiary;
}
return Container(
margin: const EdgeInsets.symmetric(vertical: 2),
padding: const EdgeInsets.all(3),
width: 300,
decoration: BoxDecoration(
border: Border.all(
width: 1.5,
),
color: backgroundColor,
borderRadius: const BorderRadius.all(Radius.circular(7)),
boxShadow: [
BoxShadow(
color: const Color(0x00000000).withOpacity(0.25),
spreadRadius: 1,
blurRadius: 2,
),
],
),
child: Row(
children: [
Expanded(
flex: 0,
child: Checkbox(
activeColor: Colors.green,
fillColor: WidgetStateProperty.resolveWith(
(states) {
if (states.contains(WidgetState.selected)) {
return null;
}
return Theme.of(context).colorScheme.onSecondary;
},
),
value: widget.completed,
onChanged: _toggleChecked,
),
),
Expanded(
flex: 2,
child: Column(
children: [
Text(
widget.todo,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onSecondary,
),
),
Text(
dateText,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onSecondary,
),
),
],
),
),
Expanded(
flex: 0,
child: IconButton(
onPressed: () => _editPopup(widget.stringRepr()),
icon: Icon(
Icons.edit,
color: Theme.of(context).colorScheme.onSecondary,
),
),
),
Expanded(
flex: 0,
child: IconButton(
onPressed: () => setState(() {
widget.deleteItem(ref);
}),
icon: Icon(
Icons.delete,
color: Theme.of(context).colorScheme.onSecondary,
),
),
),
],
),
);
}
}
// ignore: must_be_immutable
class CompoundWidgetTasks extends ConsumerStatefulWidget {
CompoundWidgetTasks({required this.item, super.key});
TasksItem item;
double getProgress() {
if (item.completedTaskList.isNotEmpty) {
return item.completedTaskList.length /
(item.taskList.length + item.completedTaskList.length);
}
return 0;
}
@override
ConsumerState<CompoundWidgetTasks> createState() =>
_CompoundWidgetTasksState();
}
class _CompoundWidgetTasksState extends ConsumerState<CompoundWidgetTasks> {
final TextEditingController _todoFieldController = TextEditingController();
void _toggleExpansion() {
setState(() {
widget.item = widget.item.copyWith(isExpanded: !widget.item.isExpanded);
ref.watch(homeControllerProvider).edit(widget.item);
});
}
IndividualTodoWidget _todoFromString(String input) {
final toConvert = input.split(seperator);
final todo = toConvert.elementAtOrNull(0)!;
final due = DateTime.parse(toConvert.elementAtOrNull(1)!);
final createdOn = DateTime.parse(toConvert.elementAtOrNull(2)!);
final completedOn = DateTime.parse(toConvert.elementAtOrNull(3)!);
final completed = toConvert.elementAtOrNull(4)!.toLowerCase() == 'true';
final isVisible = toConvert.elementAt(5).toLowerCase() == 'true';
final newItem = IndividualTodoWidget(
todo: todo,
due: due,
completed: completed,
completedOn: completedOn,
item: widget.item,
isVisible: isVisible,
createdOn: createdOn,
);
return newItem;
}
List<Widget> _buildTodoList() {
final items = <IndividualTodoWidget>[];
final taskList = <String>[...widget.item.taskList];
final completedTaskList = <String>[...widget.item.completedTaskList];
taskList.sort(
(a, b) =>
DateTime.parse(a.split(seperator).elementAtOrNull(1)!).compareTo(
DateTime.parse(b.split(seperator).elementAtOrNull(1)!),
),
);
completedTaskList.sort(
(a, b) =>
DateTime.parse(a.split(seperator).elementAtOrNull(1)!).compareTo(
DateTime.parse(b.split(seperator).elementAtOrNull(1)!),
),
);
for (var i = 0; i < taskList.length; i++) {
final todoAsString = taskList.elementAt(i);
final newItem = _todoFromString(todoAsString);
if (newItem.isVisible) {
items.add(newItem);
}
}
for (var i = 0; i < completedTaskList.length; i++) {
final todoAsString = completedTaskList.elementAt(i);
final newItem = _todoFromString(todoAsString);
if (newItem.isVisible) {
items.add(newItem);
}
}
return items;
}
void _addItem(String toAdd) {
logger.i('ITEM TO ADD: $toAdd');
final controller = ref.watch(homeControllerProvider);
final toCopy = <String>[...widget.item.taskList];
// ignore: cascade_invocations
toCopy.add(toAdd);
widget.item = widget.item.copyWith(taskList: toCopy);
setState(() {
controller.edit(widget.item);
});
}
@override
Widget build(BuildContext context) {
ref.watch(itemsProvider);
return Container(
margin: const EdgeInsets.only(left: 10, top: 10, right: 10, bottom: 10),
padding: const EdgeInsets.only(left: 10, top: 15, right: 10, bottom: 15),
//height: 100,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
borderRadius: const BorderRadius.all(Radius.circular(7)),
boxShadow: [
BoxShadow(
color: const Color(0x00000000).withOpacity(0.25),
spreadRadius: 2,
blurRadius: 5,
// changes position of shadow
),
],
),
width: double.infinity,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (!widget.item.isExpanded) ...[
Icon(
Icons.task,
size: 20,
color: Theme.of(context).colorScheme.onPrimary,
),
Expanded(
flex: 2,
child: Container(
margin: const EdgeInsets.only(left: 10, right: 10),
// Width of the progress bar
height: 30, // Height of the progress bar
child: ClipRRect(
borderRadius: const BorderRadius.all(
Radius.circular(5),
), // Rounded corners
child: LinearProgressIndicator(
value:
widget.getProgress(), // Progress value (0.0 - 1.0)
backgroundColor:
Colors.grey.withOpacity(0.5), // Background color
valueColor: const AlwaysStoppedAnimation<Color>(
Color(0xffA4E8FD),
), // Progress color
),
),
),
),
Expanded(
child: Text(
widget.item.name,
style: TextStyle(
// fontSize: 12,
// fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
],
if (widget.item.isExpanded) ...[
Expanded(
child: Text(
widget.item.name,
textScaler: const TextScaler.linear(2),
style: TextStyle(
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
Expanded(
flex: 0,
child: IconButton(
icon: Icon(
Icons.settings,
color: Theme.of(context).colorScheme.onPrimary,
),
onPressed: () => _showSettingsMenu(ref, context),
),
),
],
Expanded(
flex: 0,
child: IconButton(
icon: Icon(
widget.item.isExpanded
? Icons.arrow_drop_up_outlined
: Icons.arrow_drop_down_circle_outlined,
color: Theme.of(context).colorScheme.onPrimary,
),
onPressed: _toggleExpansion,
),
//child: Icon(Icons.arrow_drop_down_circle_outlined),
),
],
),
if (widget.item.isExpanded) ...[
// Additional child elements when expanded
SizedBox(
width: MediaQuery.of(context).size.width,
child: Column(
children: [
Column(
children: [
OutlinedButton(
onPressed: _addPopup,
child: Row(
children: [
Icon(
Icons.add_task_outlined,
color: Theme.of(context).colorScheme.onPrimary,
),
Text(
// ignore: lines_longer_than_80_chars
' ${AppLocalizations.of(context)!.tasksWidget_addTaskButtonLabel}',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color:
Theme.of(context).colorScheme.onPrimary,
),
),
],
),
),
ListView(
padding: const EdgeInsets.symmetric(vertical: 5),
shrinkWrap: true,
physics: const ClampingScrollPhysics(),
children: _buildTodoList(),
),
if (anyTasksCompleted()) ...[
OutlinedButton(
onPressed: _deleteCompletedTasks,
child: Text(
AppLocalizations.of(context)!
.tasksWidget_deleteDone,
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color:
Theme.of(context).colorScheme.onPrimary,
),
),
),
],
/* if (anyTasksOverdue()) ...[
OutlinedButton(
onPressed: _rescheduleOverdueTasks,
child: Text(
AppLocalizations.of(context)!
.tasksWidget_rescheduleOverdue,
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color:
Theme.of(context).colorScheme.onPrimary,
),
),
),
],*/
],
),
],
),
),
],
],
),
);
}
bool anyTasksCompleted() {
for (final item in widget.item.completedTaskList) {
final toConvert = item.split(seperator);
final isVisible = toConvert.elementAt(5).toLowerCase() == 'true';
if (isVisible) {
return true;
}
}
return false;
}
void _deleteCompletedTasks() {
final controller = ref.watch(homeControllerProvider);
final newCompletedTaskList = <String>[]; // widget.item.completedTaskList;
for (final itemString in widget.item.completedTaskList) {
final oldItem = _todoFromString(itemString);
final dateFormat = DateFormat('yyyy-MM-dd');
final newItem = [
oldItem.todo,
dateFormat.format(oldItem.due),
oldItem.createdOn,
oldItem.completedOn,
oldItem.completed,
false,
].join(seperator);
newCompletedTaskList.add(newItem);
}
widget.item = widget.item.copyWith(completedTaskList: newCompletedTaskList);
controller.edit(widget.item);
}
bool anyTasksOverdue() {
final taskList = <String>[...widget.item.taskList];
for (final taskString in taskList) {
if (_todoFromString(taskString).overdue()) {
return true;
}
}
return false;
}
// Used for the Reschedule Task Button
// ignore: unused_element
void _rescheduleOverdueTasks() {
final taskList = <String>[...widget.item.taskList];
for (var i = 0; i < taskList.length; i++) {
final taskString = taskList[i];
if (_todoFromString(taskString).overdue()) {
final oldItem = _todoFromString(taskString);
final now = DateTime.now();
final newItem = IndividualTodoWidget(
todo: oldItem.todo,
due: DateTime(now.year, now.month, now.day),
createdOn: DateTime.now(),
completedOn: DateTime.now(),
completed: oldItem.completed,
item: widget.item,
isVisible: true,
);
taskList.replaceRange(i, i + 1, [newItem.stringRepr()]);
final controller = ref.watch(homeControllerProvider);
widget.item = widget.item.copyWith(taskList: taskList);
setState(
() {
controller.edit(widget.item);
},
);
}
}
}
Future<void> _addPopup() async {
DateTime? date = DateTime.now();
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
title: Text(
AppLocalizations.of(context)!.tasksWidget_addTaskButtonLabel,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimaryContainer,
fontWeight: FontWeight.bold,
fontSize: 20,
),
),
scrollable: true,
content: Column(
children: <Widget>[
TextField(
controller: _todoFieldController,
autofillHints: null,
decoration: InputDecoration(
hintText: AppLocalizations.of(context)!.taskSettings_name,
),
),
TextButton(
child: Text(
AppLocalizations.of(context)!.taskSettings_duePicker,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
),
onPressed: () async {
final pickedDate = await showDatePicker(
context: context,
initialDate: DateTime.now(), //get today's date
firstDate: DateTime.now(),
//DateTime.now() - not to allow to choose before today
lastDate: DateTime(2101),
);
date = pickedDate;
},
),
],
),
actions: <Widget>[
OutlinedButton(
style: OutlinedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
AppLocalizations.of(context)!.widgetSettings_cancelButton,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed: () {
//DateTime due = DateTime.parse(formattedDate);
// ignore: cascade_invocations
if (_todoFieldController.text.isEmpty) {
_todoFieldController.text = 'todo';
}
if (date != null && _todoFieldController.text.isNotEmpty) {
final dateFormat = DateFormat('yyyy-MM-dd');
final formattedDate = dateFormat.format(date!);
Navigator.of(context).pop();
final itemToAdd = [
_todoFieldController.text,
formattedDate,
DateTime.now(),
DateTime.now(),
false,
true,
].join(seperator);
_addItem(itemToAdd);
_todoFieldController.clear();
}
},
child: Text(
AppLocalizations.of(context)!.widgetSettings_saveButton,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
],
);
},
);
}
Future<void> _showSettingsMenu(WidgetRef ref, BuildContext context) async {
final settingEntries = WidgetSettingsData(
entries: {
'name': SettingEntryText(
name: AppLocalizations.of(context)!.widgetSettings_name,
defaultValue: widget.item.name,
),
},
);
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
content: WidgetSettings(
entries: settingEntries,
),
actions: [
OutlinedButton(
style: OutlinedButton.styleFrom(
backgroundColor: Colors.red,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed: () {
ref
.watch(homeControllerProvider)
.edit(widget.item.copyWith(isVisible: false));
// await controller.delete(widget.item.id);
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
},
child: Text(
AppLocalizations.of(context)!.widgetSettings_deleteButton,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
fontWeight: FontWeight.bold,
),
),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed: () {
final controller = ref.watch(homeControllerProvider);
final name = settingEntries.getValue('name') as String;
widget.item = widget.item.copyWith(name: name);
setState(() {
widget.item = widget.item.copyWith(name: name);
controller.edit(widget.item);
});
Navigator.of(context).pop();
},
child: Text(
AppLocalizations.of(context)!.widgetSettings_saveButton,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.bold,
),
),
),
],
);
},
);
}
}

View file

@ -0,0 +1,491 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:habitrack_app/function_widgets/widget_settings_menu/setting_entry.dart';
import 'package:habitrack_app/function_widgets/widget_settings_menu/widget_settings.dart';
import 'package:habitrack_app/infrastructure/widget_wall/items_controller.dart';
import 'package:habitrack_app/infrastructure/widget_wall/items_state.dart';
import 'package:habitrack_app/main.dart';
import 'package:habitrack_app/sembast/hydration.dart';
import 'package:liquid_progress_indicator_v2/liquid_progress_indicator.dart';
// ignore: must_be_immutable
class CompoundWidgetWater extends ConsumerStatefulWidget {
CompoundWidgetWater({required this.item, super.key});
late Hydration item;
double getProgress() {
return item.current / item.goal;
}
@override
ConsumerState<CompoundWidgetWater> createState() =>
_CompoundWidgetWaterState();
}
class _CompoundWidgetWaterState extends ConsumerState<CompoundWidgetWater> {
void _toggleExpansion() {
setState(() {
widget.item = widget.item.copyWith(isExpanded: !widget.item.isExpanded);
ref.watch(homeControllerProvider).edit(widget.item);
});
}
void _addQuantity(int toAdd) {
setState(() {
final controller = ref.watch(homeControllerProvider);
final oldval = widget.item.current;
logger.i('Old item: ${widget.item}');
widget.item = widget.item.copyWith(current: oldval + toAdd);
logger.i('New item: ${widget.item}');
controller.edit(widget.item);
});
}
@override
Widget build(BuildContext context) {
ref.watch(itemsProvider);
return Container(
margin: const EdgeInsets.only(left: 10, top: 10, right: 10, bottom: 10),
padding: const EdgeInsets.only(left: 10, top: 15, right: 10, bottom: 15),
//height: 100,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
borderRadius: const BorderRadius.all(Radius.circular(7)),
boxShadow: [
BoxShadow(
color: const Color(0x00000000).withOpacity(0.25),
spreadRadius: 2,
blurRadius: 5,
// changes position of shadow
),
],
),
width: double.infinity,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (!widget.item.isExpanded) ...[
Icon(
Icons.local_drink,
size: 20,
color: Theme.of(context).colorScheme.onPrimary,
),
Expanded(
flex: 2,
child: Container(
margin: const EdgeInsets.only(left: 10, right: 10),
// Width of the progress bar
height: 30, // Height of the progress bar
child: ClipRRect(
borderRadius: const BorderRadius.all(
Radius.circular(5),
), // Rounded corners
child: LinearProgressIndicator(
value:
widget.getProgress(), // Progress value (0.0 - 1.0)
backgroundColor:
Colors.grey.withOpacity(0.5), // Background color
valueColor: const AlwaysStoppedAnimation<Color>(
Color(0xffA4E8FD),
), // Progress color
),
),
),
),
Expanded(
child: Text(
widget.item.name,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
],
if (widget.item.isExpanded) ...[
Expanded(
child: Text(
widget.item.name,
textScaler: const TextScaler.linear(2),
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
Expanded(
flex: 0,
child: IconButton(
icon: Icon(
Icons.settings,
color: Theme.of(context).colorScheme.onPrimary,
),
onPressed: () => _showSettingsMenu(ref),
),
//child: Icon(Icons.arrow_drop_down_circle_outlined),
),
],
Expanded(
flex: 0,
child: IconButton(
icon: Icon(
widget.item.isExpanded
? Icons.arrow_drop_up_outlined
: Icons.arrow_drop_down_circle_outlined,
color: Theme.of(context).colorScheme.onPrimary,
),
onPressed: _toggleExpansion,
),
//child: Icon(Icons.arrow_drop_down_circle_outlined),
),
],
),
if (widget.item.isExpanded) ...[
// Additional child elements when expanded
SizedBox(
height: 300,
width: MediaQuery.of(context).size.width,
child: Column(
children: [
//INSERT WIDGET SPECIFIC STUFF HERE
SizedBox(
height: 300,
width: MediaQuery.of(context).size.width,
child: Row(
children: [
Expanded(
child: OvershootLiquidLinearProgressIndicator(
current: widget.item.current,
goal: widget.item.goal,
),
),
Expanded(
child: Column(
children: [
Text(
'${widget.item.current / 1000} / ${widget.item.goal / 1000} L',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color: Theme.of(context)
.colorScheme
.onPrimary,
),
),
Expanded(
child: OutlinedButton(
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(
Theme.of(context).colorScheme.secondary,
),
),
onPressed: () => {
_addQuantity(
widget.item.button1Amount,
),
},
child: Text(
'${widget.item.button1Amount} mL',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color: Theme.of(context)
.colorScheme
.onSecondary,
),
),
),
),
Expanded(
child: OutlinedButton(
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(
Theme.of(context).colorScheme.secondary,
),
),
onPressed: () => {
_addQuantity(
widget.item.button2Amount,
),
},
child: Text(
'${widget.item.button2Amount} mL',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color: Theme.of(context)
.colorScheme
.onPrimary,
),
),
),
),
Expanded(
child: OutlinedButton(
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(
Theme.of(context).colorScheme.secondary,
),
),
onPressed: _showPopupCustomAmount,
child: Text(
AppLocalizations.of(context)!
.waterWidget_customAmountButton,
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color: Theme.of(context)
.colorScheme
.onPrimary,
),
),
),
),
],
),
),
],
),
),
],
),
),
// Add more widgets here as needed
],
],
),
);
}
Future<void> _showSettingsMenu(WidgetRef ref) async {
final settingEntries = WidgetSettingsData(
entries: {
'name': SettingEntryText(
name: AppLocalizations.of(context)!.widgetSettings_name,
defaultValue: widget.item.name,
),
'button1Amount': SettingEntryNumeric(
name: AppLocalizations.of(context)!.waterWidgetSettings_button1,
defaultValue: widget.item.button1Amount,
),
'button2Amount': SettingEntryNumeric(
name: AppLocalizations.of(context)!.waterWidgetSettings_button2,
defaultValue: widget.item.button2Amount,
),
'targetGoal': SettingEntrySlider(
name: AppLocalizations.of(context)!.waterWidgetSettings_goal,
defaultValue: double.parse(widget.item.goal.toString()),
divisions: 80,
topValue: 4000,
),
'currentAmount': SettingEntryNumeric(
name: AppLocalizations.of(context)!.waterWidgetSettings_current,
defaultValue: widget.item.current,
),
},
);
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
content: WidgetSettings(
entries: settingEntries,
),
actions: [
OutlinedButton(
style: OutlinedButton.styleFrom(
backgroundColor: Colors.red,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed: () {
//widget.parent.delete(ref);
ref.watch(itemsProvider);
final controller = ref.watch(homeControllerProvider);
widget.item = widget.item.copyWith(isVisible: false);
controller.edit(widget.item);
logger.i('Attempting delete');
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
},
child: Text(
AppLocalizations.of(context)!.widgetSettings_deleteButton,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
fontWeight: FontWeight.bold,
),
),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.onPrimary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed: () {
final controller = ref.watch(homeControllerProvider);
logger.i('Attempting edit of water widget stuff');
// widget.settingEntries.notify();
final name = settingEntries.getValue('name') as String;
final currentAmount =
settingEntries.getValue('currentAmount') as int;
final targetGoal =
(settingEntries.getValue('targetGoal') as double).round();
final button1Amount =
settingEntries.getValue('button1Amount') as int;
final button2Amount =
settingEntries.getValue('button2Amount') as int;
widget.item = widget.item.copyWith(
name: name,
current: currentAmount,
goal: targetGoal,
button1Amount: button1Amount,
button2Amount: button2Amount,
);
controller.edit(widget.item);
Navigator.of(context).pop();
},
child: Text(
AppLocalizations.of(context)!.widgetSettings_saveButton,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.bold,
),
),
),
],
);
},
);
}
Future<void> _showPopupCustomAmount() async {
final customFieldController = TextEditingController();
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
scrollable: true,
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
title: Text(
AppLocalizations.of(context)!.waterWidget_customAmountMessage,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
),
content: Column(
children: <Widget>[
TextField(
controller: customFieldController,
keyboardType: TextInputType.number,
autofocus: true,
),
],
),
actions: <Widget>[
ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
backgroundColor: Theme.of(context).colorScheme.primary,
),
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
AppLocalizations.of(context)!.widgetSettings_cancelButton,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed: () {
Navigator.of(context).pop();
final result = int.tryParse(customFieldController.text) ?? 0;
_addQuantity(result);
},
child: Text(
AppLocalizations.of(context)!.widgetSettings_saveButton,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
],
);
},
);
}
}
class OvershootLiquidLinearProgressIndicator extends StatelessWidget {
const OvershootLiquidLinearProgressIndicator({
required this.current,
required this.goal,
super.key,
});
final int current;
final int goal;
@override
Widget build(BuildContext context) {
var mainColor = Colors.blue;
var backColor = Colors.white;
var value = 0.0;
if (current < goal) {
mainColor = Colors.blue;
backColor = Colors.white;
value = (current - goal * 0) / goal;
} else if (current < goal * 2) {
mainColor = Colors.orange;
backColor = Colors.blue;
value = (current - goal * 1) / goal;
} else {
mainColor = Colors.red;
backColor = Colors.orange;
value = (current - goal * 2) / goal;
}
return LiquidLinearProgressIndicator(
value: value,
valueColor: AlwaysStoppedAnimation(
mainColor,
),
backgroundColor: backColor,
borderColor: Colors.black,
borderWidth: 3,
borderRadius: 12,
direction: Axis.vertical,
);
}
}

View file

@ -0,0 +1,375 @@
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(() {});
}
}

View file

@ -0,0 +1,79 @@
// ignore_for_file: public_member_api_docs
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:habitrack_app/function_widgets/widget_settings_menu/setting_entry.dart';
class WidgetSettingsData {
WidgetSettingsData({required this.entries});
final Map<State, void Function(void Function())> listeners = {};
final Map<String, SettingEntry> entries;
void addListener(State toAdd, void Function(void Function()) updateCall) {
listeners[toAdd] = updateCall;
}
void removeListener(State toRemove) {
listeners.remove(toRemove);
}
void notify() {
for (final listener in listeners.keys) {
listeners[listener]!(() => ());
}
}
dynamic getValue(String key) {
return entries[key]?.getValue();
}
void setValue(String key, dynamic value) {
entries[key]?.setValue(value);
notify();
}
List<SettingEntry> asList() {
return entries.values.toList();
}
}
class WidgetSettings extends StatefulWidget {
const WidgetSettings({
required this.entries,
super.key,
});
final WidgetSettingsData entries;
@override
State<WidgetSettings> createState() => _WidgetSettingsState();
}
class _WidgetSettingsState extends State<WidgetSettings> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
iconTheme: IconThemeData(
color: Theme.of(context).colorScheme.onPrimaryContainer,),
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
title: Text(
AppLocalizations.of(context)!.settingsHeader,
textScaler: const TextScaler.linear(1.5),
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimaryContainer,
fontWeight: FontWeight.bold,
fontSize: 15,
),
),
),
body: ColoredBox(
color: Theme.of(context).colorScheme.primaryContainer,
child: ListView(
children: widget.entries.asList(),
),
),
);
}
}