Initial (redacted) commit.
This commit is contained in:
commit
655f8a036a
368 changed files with 20949 additions and 0 deletions
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
375
app/lib/function_widgets/widget_settings_menu/setting_entry.dart
Normal file
375
app/lib/function_widgets/widget_settings_menu/setting_entry.dart
Normal 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(() {});
|
||||
}
|
||||
}
|
|
@ -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(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue