Merge branch '24-timer-darf-nur-ein-mal-pro-tag-gedruckt-werden' into 'main'

Tasks können nur ausgeführt werden, wenn gefordert.

See merge request Crondung/hsma_cpd!25
main
Julian Gegner 2023-03-04 09:06:38 +00:00
commit 42f4fcd05d
29 changed files with 444 additions and 92 deletions

View File

@ -5,6 +5,7 @@ import 'package:smoke_cess_app/providers/tasks_provider.dart';
import 'package:smoke_cess_app/services/notification_service.dart';
import 'package:timezone/data/latest.dart' as tz;
import 'globals.dart' as globals;
import 'providers/page_provider.dart';
import 'providers/settings_provider.dart';
void main() {
@ -24,9 +25,7 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: MultiProvider(
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => SettingsProvider()),
ChangeNotifierProxyProvider<SettingsProvider, TasksProvider>(
@ -34,8 +33,13 @@ class MyApp extends StatelessWidget {
update: (context, value, TasksProvider? previous) =>
TasksProvider(value),
),
ChangeNotifierProvider(
create: (context) => PageProvider(),
),
],
child: const MyHomePage(),
child: const MaterialApp(
title: _title,
home: MyHomePage(),
));
}
}

View File

@ -8,6 +8,7 @@ class Mood implements DatabaseRecord {
Mood(this._moodValue, this._comment, this._date);
DateTime get date => _date;
int get moodValue => _moodValue;
@override
factory Mood.fromDatabase(Map<String, dynamic> map) {

View File

@ -7,6 +7,9 @@ class Relapse implements DatabaseRecord {
Relapse(this._category, this._comment, this._date);
String get category => _category;
DateTime get date => _date;
@override
factory Relapse.fromDatabase(Map<String, dynamic> map) {
DateTime date = DateTime.parse(map['date']);

View File

@ -12,6 +12,7 @@ class Sleep implements DatabaseRecord {
this._wokeUpAt);
DateTime get date => _date;
int get sleepQualitiyValue => _sleepQualityValue;
@override
factory Sleep.fromDatabase(Map<String, dynamic> map) {

View File

@ -8,6 +8,8 @@ class Workout implements DatabaseRecord {
Workout(this._motivationBefore, this._motivationAfter, this._workoutDate);
DateTime get date => _workoutDate;
int get motivationBefore => _motivationBefore;
int get motivationAfter => _motivationAfter;
@override
factory Workout.fromDatabase(Map<String, dynamic> map) {

View File

@ -1,32 +1,16 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:smoke_cess_app/providers/timer_provider.dart';
import 'package:smoke_cess_app/providers/workout_provider.dart';
import 'package:smoke_cess_app/widgets/mute_button.dart';
import 'package:smoke_cess_app/widgets/workout_timer_widget.dart';
import 'package:smoke_cess_app/services/pages_service.dart';
import 'package:smoke_cess_app/widgets/workout_form.dart';
import 'package:smoke_cess_app/widgets/workout_view.dart';
import '../widgets/view_form_page.dart';
class IntervalTimerPage extends StatelessWidget {
const IntervalTimerPage({super.key});
@override
Widget build(BuildContext context) {
TimerProvider timerProvider = TimerProvider();
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => timerProvider),
ChangeNotifierProvider(
create: (context) => WorkoutProvider(timerProvider)),
],
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Align(
alignment: Alignment.topLeft,
child: MuteButton(),
),
WorkoutTimerWidget()
],
),
);
return const ViewFormPage(
form: WorkoutForm(), view: WorkoutView(), page: Pages.timer);
}
}

View File

@ -1,6 +1,7 @@
import 'package:awesome_dialog/awesome_dialog.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:smoke_cess_app/providers/page_provider.dart';
import 'package:smoke_cess_app/providers/tasks_provider.dart';
import 'package:smoke_cess_app/services/pages_service.dart';
import 'package:smoke_cess_app/providers/settings_provider.dart';
@ -19,10 +20,14 @@ class MyHomePageState extends State<MyHomePage> {
bool _isConfigured = false;
void _onItemTapped(int index) {
PageProvider pageProvider = context.read<PageProvider>();
setState(() {
_isConfigured
? _selectedIndex = index
: AwesomeDialog(
if (_isConfigured) {
pageProvider.showForm = false;
_selectedIndex = index;
return;
}
AwesomeDialog(
context: context,
dialogType: DialogType.info,
title: 'Fehlende Konfiguration',
@ -40,9 +45,9 @@ class MyHomePageState extends State<MyHomePage> {
appBar: AppBar(
title: Text(
'${pages.values.elementAt(_selectedIndex)['title']} ${_isConfigured ? "Gruppe ${settingsModel.settings?.group}" : ""}')),
body: Center(
child: SingleChildScrollView(
child: pages.values.elementAt(_selectedIndex)['page'])),
body: SingleChildScrollView(
child: pages.values.elementAt(_selectedIndex)['page'],
),
bottomNavigationBar: NavigationBar(
onDestinationSelected: _onItemTapped,
selectedIndex: _selectedIndex,

View File

@ -1,17 +1,15 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:smoke_cess_app/providers/input_provider.dart';
import 'package:smoke_cess_app/services/pages_service.dart';
import 'package:smoke_cess_app/widgets/mood_form.dart';
import 'package:smoke_cess_app/widgets/mood_view.dart';
import 'package:smoke_cess_app/widgets/view_form_page.dart';
class MoodPage extends StatelessWidget {
const MoodPage({super.key});
@override
Widget build(BuildContext context) {
return Center(
child: ChangeNotifierProvider(
create: (context) => InputProvider(),
child: const MoodForm(),
));
return const ViewFormPage(
form: MoodForm(), view: MoodView(), page: Pages.mood);
}
}

View File

@ -1,17 +1,15 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:smoke_cess_app/services/pages_service.dart';
import 'package:smoke_cess_app/widgets/relapse_form.dart';
import '../providers/input_provider.dart';
import 'package:smoke_cess_app/widgets/relapse_view.dart';
import '../widgets/view_form_page.dart';
class RelapsePage extends StatelessWidget {
const RelapsePage({super.key});
@override
Widget build(BuildContext context) {
return Center(
child: ChangeNotifierProvider(
create: (context) => InputProvider(),
child: const RelapseForm(),
));
return const ViewFormPage(
form: RelapseForm(), view: RelapseView(), page: Pages.relapse);
}
}

View File

@ -1,17 +1,15 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:smoke_cess_app/providers/input_provider.dart';
import 'package:smoke_cess_app/services/pages_service.dart';
import 'package:smoke_cess_app/widgets/sleep_form.dart';
import 'package:smoke_cess_app/widgets/sleep_view.dart';
import 'package:smoke_cess_app/widgets/view_form_page.dart';
class SleepPage extends StatelessWidget {
const SleepPage({super.key});
@override
Widget build(BuildContext context) {
return Center(
child: ChangeNotifierProvider(
create: (context) => InputProvider(),
child: const SleepForm(),
));
return const ViewFormPage(
form: SleepForm(), view: SleepView(), page: Pages.sleep);
}
}

View File

@ -11,22 +11,16 @@ class InputProvider extends ChangeNotifier {
'wokeUpAt': const TimeOfDay(hour: 8, minute: 0),
'sleptAt': const TimeOfDay(hour: 22, minute: 0),
};
String _relapseCategory = '';
String relapseCategory = '';
double get sliderValue => _sliderValue;
TextEditingController get textController => _textController;
String get relapseCategory => _relapseCategory;
set sliderValue(double newValue) {
_sliderValue = newValue;
notifyListeners();
}
set relapseCategory(String newValue) {
_relapseCategory = newValue;
notifyListeners();
}
TimeOfDay getTimeEntry(String key) {
return _times[key] ?? const TimeOfDay(hour: 12, minute: 0);
}
@ -53,7 +47,7 @@ class InputProvider extends ChangeNotifier {
Future<int> saveRelapse() {
Relapse relapse =
Relapse(_relapseCategory, _textController.text, DateTime.now());
Relapse(relapseCategory, _textController.text, DateTime.now());
_resetFields();
return globals.databaseService.addRelapse(relapse);
}

View File

@ -0,0 +1,10 @@
import 'package:flutter/material.dart';
class PageProvider extends ChangeNotifier {
bool showForm = false;
void swap() {
showForm = !showForm;
notifyListeners();
}
}

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:smoke_cess_app/models/relapse.dart';
import 'package:smoke_cess_app/models/sleep.dart';
import 'package:smoke_cess_app/models/workout.dart';
import 'package:smoke_cess_app/providers/settings_provider.dart';
@ -15,8 +16,14 @@ class TasksProvider extends ChangeNotifier {
Pages.timer: true,
};
List<Mood> moodHistory = [];
List<Sleep> sleepHistory = [];
List<Workout> workoutHistory = [];
List<Relapse> relapseHistory = [];
TasksProvider(SettingsProvider? settingsProvider) {
initTasks();
initHistories();
}
void setTaskDone(Pages taskName) {
@ -24,13 +31,23 @@ class TasksProvider extends ChangeNotifier {
notifyListeners();
}
void initHistories() async {
moodHistory = await globals.databaseService.getMoodRecords();
sleepHistory = await globals.databaseService.getSleepRecords();
workoutHistory = await globals.databaseService.getWorkoutRecords();
relapseHistory = await globals.databaseService.getRelapseRecords();
notifyListeners();
}
void initTasks() async {
DateTime now = DateTime.now();
TZDateTime? moodToday = await getTodayMood();
if (moodToday != null) {
List<Mood> moodList = await globals.databaseService.getMoodRecords();
if (moodList.isNotEmpty) {
Mood mood = moodList.last;
tasks[Pages.mood] = !isSameDay(moodToday, mood.date);
tasks[Pages.mood] =
!isSameDay(moodToday, mood.date) && moodToday.isBefore(now);
}
} else {
tasks[Pages.mood] = false;
@ -40,7 +57,8 @@ class TasksProvider extends ChangeNotifier {
List<Sleep> sleepList = await globals.databaseService.getSleepRecords();
if (sleepList.isNotEmpty) {
Sleep sleep = sleepList.last;
tasks[Pages.sleep] = !isSameDay(sleepToday, sleep.date);
tasks[Pages.sleep] =
!isSameDay(sleepToday, sleep.date) && sleepToday.isBefore(now);
}
} else {
tasks[Pages.sleep] = false;

View File

@ -21,5 +21,14 @@ class TimerProvider extends ChangeNotifier {
void stopTimer() {
started = false;
_timer?.cancel();
_timer = null;
}
@override
void dispose() {
started = false;
_timer?.cancel();
_timer = null;
super.dispose();
}
}

View File

@ -92,11 +92,25 @@ class WorkoutProvider extends ChangeNotifier {
notifyListeners();
}
void interruptWorkout() {
isWorkoutStarted = false;
isWorkoutComplete = false;
_audioPlayer.stop();
timerProvider.stopTimer();
notifyListeners();
}
void saveWorkout() {
Workout workout =
Workout(motivationBefore, motivationAfter, DateTime.now());
globals.databaseService.addWorkout(workout);
}
@override
void dispose() {
interruptWorkout();
super.dispose();
}
}
Map<String, Map<String, dynamic>> _workoutPhaseSettings = {

View File

@ -1,6 +1,8 @@
import 'package:smoke_cess_app/services/settings_service.dart';
import 'package:timezone/timezone.dart';
import 'pages_service.dart';
const int trainingTime = 40;
const weekDays = {
@ -47,6 +49,45 @@ Future<TZDateTime?> getTodaySleep() async {
return today.isNotEmpty ? today.first : null;
}
Future<Duration> getTimeTillNextMood() async {
List<TZDateTime> moodDates = await getDatesforMood();
Iterable<TZDateTime> nextDate =
moodDates.where((element) => element.isAfter(DateTime.now()));
Duration duration = nextDate.isNotEmpty
? nextDate.first.difference(DateTime.now())
: const Duration(seconds: 0);
return duration;
}
Future<Duration> getTimeTillNextSleep() async {
List<TZDateTime> sleepDates = await getDatesforSleep();
Iterable<TZDateTime> nextDate =
sleepDates.where((element) => element.isAfter(DateTime.now()));
Duration duration = nextDate.isNotEmpty
? nextDate.first.difference(DateTime.now())
: const Duration(seconds: 0);
return duration;
}
Future<Duration> getTimeTillNextWorkout() async {
DateTime now = DateTime.now();
DateTime tomorrow =
DateTime(now.year, now.month, now.day).add(const Duration(days: 1));
Duration duration = tomorrow.difference(now);
return duration;
}
Future<Duration> getTimeTill(Pages page) {
switch (page) {
case Pages.mood:
return getTimeTillNextMood();
case Pages.sleep:
return getTimeTillNextSleep();
default:
return getTimeTillNextWorkout();
}
}
List<TZDateTime> createTZDateTimes(
List<String>? selectedDays, int? selectedHours, int? selectedMinutes) {
final List<TZDateTime> tzDateTimes = [];

View File

@ -1,4 +1,14 @@
String formatTime(int seconds) {
Duration duration = Duration(seconds: seconds);
return '${duration.inMinutes.remainder(60).toString().padLeft(2, '0')}:${duration.inSeconds.remainder(60).toString().padLeft(2, '0')}';
String formattedTime = '';
String twoDigits(int n) => n.toString().padLeft(2, "0");
String days = duration.inDays.toString();
String hours = twoDigits(duration.inHours.remainder(24));
String minutes = twoDigits(duration.inMinutes.remainder(60));
String formattedSeconds = twoDigits(duration.inSeconds.remainder(60));
if (duration.inDays != 0) formattedTime += '$days:';
if (duration.inHours != 0) formattedTime += '$hours:';
formattedTime += '$minutes:';
formattedTime += formattedSeconds;
return formattedTime;
}

View File

@ -9,6 +9,7 @@ class DropDown extends StatelessWidget {
@override
Widget build(BuildContext context) {
var inputModel = context.watch<InputProvider>();
inputModel.relapseCategory = _items.isNotEmpty ? _items[0] : '';
return DropdownButtonFormField<String>(
value: _items.isEmpty ? null : _items[0],
icon: const Icon(Icons.arrow_downward),

View File

@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:syncfusion_flutter_charts/charts.dart';
import '../models/mood.dart';
import '../providers/tasks_provider.dart';
class MoodView extends StatelessWidget {
const MoodView({super.key});
@override
Widget build(BuildContext context) {
var tasksModel = context.watch<TasksProvider>();
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SfCartesianChart(
primaryXAxis: DateTimeAxis(),
series: <ChartSeries>[
LineSeries<Mood, DateTime>(
dataSource: tasksModel.moodHistory,
xValueMapper: (Mood value, _) => value.date,
yValueMapper: (Mood value, _) => value.moodValue)
],
),
Column(
children: tasksModel.moodHistory.map((mood) {
return Text('${mood.date}: ${mood.moodValue}');
}).toList())
],
);
}
}

View File

@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:smoke_cess_app/providers/timer_provider.dart';
import 'package:smoke_cess_app/widgets/timer_widget.dart';
import '../services/date_service.dart';
import '../services/pages_service.dart';
void showTaskDonePopup(BuildContext context, Pages page) async {
Duration duration = await getTimeTill(page);
await showDialog(
context: context,
builder: (BuildContext context) {
return ChangeNotifierProvider(
create: (context) => TimerProvider(),
child: TaskDonePopup(
duration: duration,
),
);
},
);
}
class TaskDonePopup extends StatelessWidget {
final Duration duration;
const TaskDonePopup({super.key, required this.duration});
@override
Widget build(BuildContext context) {
TimerProvider timerProvider = context.read<TimerProvider>();
timerProvider.startTimer(duration);
return AlertDialog(
title: const Text('Schon gemacht'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Nächstes mal wieder:'),
TimerWidget(duration: duration)
]));
}
}

View File

@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/tasks_provider.dart';
class RelapseView extends StatelessWidget {
const RelapseView({super.key});
@override
Widget build(BuildContext context) {
var tasksModel = context.watch<TasksProvider>();
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: tasksModel.relapseHistory.map((relapse) {
return Text('${relapse.date}: ${relapse.category}');
}).toList());
}
}

View File

@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:smoke_cess_app/models/sleep.dart';
import 'package:syncfusion_flutter_charts/charts.dart';
import '../providers/tasks_provider.dart';
class SleepView extends StatelessWidget {
const SleepView({super.key});
@override
Widget build(BuildContext context) {
var tasksModel = context.watch<TasksProvider>();
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SfCartesianChart(
primaryXAxis: DateTimeAxis(),
series: <ChartSeries>[
LineSeries<Sleep, DateTime>(
dataSource: tasksModel.sleepHistory,
xValueMapper: (Sleep value, _) => value.date,
yValueMapper: (Sleep value, _) => value.sleepQualitiyValue)
],
),
Column(
children: tasksModel.sleepHistory.map((sleep) {
return Text('${sleep.date}: ${sleep.sleepQualitiyValue}');
}).toList())
],
);
}
}

View File

@ -1,5 +1,7 @@
import 'package:awesome_dialog/awesome_dialog.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:smoke_cess_app/providers/page_provider.dart';
class SubmitFormButton extends StatelessWidget {
final Future<int> Function() submitCallback;
@ -9,21 +11,23 @@ class SubmitFormButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
PageProvider pageProvider = context.watch<PageProvider>();
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: ElevatedButton(
onPressed: () async {
int success = await submitCallback();
if (success != 0) {
AwesomeDialog(
await AwesomeDialog(
context: context,
dialogType: DialogType.success,
title: 'Gespeichert',
desc: 'Der Eintrag wurde erfolgreich gespeichert',
).show();
updateTasks();
pageProvider.swap();
} else {
AwesomeDialog(
await AwesomeDialog(
context: context,
dialogType: DialogType.error,
title: 'Fehler',

View File

@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:smoke_cess_app/services/pages_service.dart';
import '../providers/input_provider.dart';
import '../providers/page_provider.dart';
import '../providers/tasks_provider.dart';
import 'popup_for_task_done.dart';
class ViewFormPage extends StatelessWidget {
final Widget form;
final Widget view;
final Pages page;
const ViewFormPage(
{super.key, required this.form, required this.view, required this.page});
@override
Widget build(BuildContext context) {
PageProvider pageProvider = context.watch<PageProvider>();
TasksProvider tasksProvider = context.watch<TasksProvider>();
return Wrap(children: [
Align(
alignment: Alignment.topLeft,
child: IconButton(
icon: pageProvider.showForm
? const Icon(Icons.arrow_back, color: Colors.black)
: const Icon(Icons.add_outlined, color: Colors.black),
onPressed: tasksProvider.tasks[page] ?? true
? pageProvider.swap
: () => showTaskDonePopup(context, page),
),
),
pageProvider.showForm
? Center(
child: ChangeNotifierProvider(
create: (context) => InputProvider(),
child: form,
))
: Center(child: view)
]);
}
}

View File

@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/timer_provider.dart';
import '../providers/workout_provider.dart';
import 'mute_button.dart';
import 'workout_timer_widget.dart';
class WorkoutForm extends StatelessWidget {
const WorkoutForm({super.key});
@override
Widget build(BuildContext context) {
TimerProvider timerProvider = TimerProvider();
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => timerProvider),
ChangeNotifierProvider(
create: (context) => WorkoutProvider(timerProvider)),
],
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Align(
alignment: Alignment.topLeft,
child: MuteButton(),
),
WorkoutTimerWidget()
],
));
}
}

View File

@ -1,7 +1,9 @@
import 'dart:async';
import 'package:awesome_dialog/awesome_dialog.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:smoke_cess_app/providers/page_provider.dart';
import 'package:smoke_cess_app/providers/tasks_provider.dart';
import 'package:smoke_cess_app/providers/workout_provider.dart';
import 'package:smoke_cess_app/services/pages_service.dart';
@ -18,19 +20,29 @@ class WorkoutTimerWidget extends StatelessWidget {
TimerProvider timerProvider = context.watch<TimerProvider>();
WorkoutProvider workoutProvider = context.watch<WorkoutProvider>();
TasksProvider tasksProvider = context.read<TasksProvider>();
PageProvider pageProvider = context.read<PageProvider>();
void handleStopWorkout() async {
await showMotivationPopup(context, (double value) {
workoutProvider.motivationAfter = value.toInt();
workoutProvider.saveWorkout();
tasksProvider.setTaskDone(Pages.timer);
}, 'Motivation nach dem Training');
await AwesomeDialog(
context: context,
dialogType: DialogType.success,
title: 'Gespeichert',
desc: 'Der Eintrag wurde erfolgreich gespeichert',
).show();
pageProvider.swap();
}
if (workoutProvider.isPhaseComplete && !workoutProvider.isWorkoutComplete) {
Timer(const Duration(milliseconds: 1), () => workoutProvider.nextPhase());
}
if (workoutProvider.isWorkoutComplete) {
Timer(
const Duration(milliseconds: 1),
() => showMotivationPopup(context, (double value) {
workoutProvider.motivationAfter = value.toInt();
workoutProvider.saveWorkout();
tasksProvider.setTaskDone(Pages.timer);
}, 'Motivation nach dem Training'));
Timer(const Duration(milliseconds: 1), handleStopWorkout);
}
void handleStartStopWorkout() {
@ -40,11 +52,8 @@ class WorkoutTimerWidget extends StatelessWidget {
workoutProvider.startWorkout();
}, 'Motivation vor dem Training');
} else {
workoutProvider.stopWorkout();
showMotivationPopup(
context,
(double value) => workoutProvider.motivationAfter = value.toInt(),
'Motivation nach dem Training');
workoutProvider.interruptWorkout();
handleStopWorkout();
}
}

View File

@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:smoke_cess_app/models/workout.dart';
import 'package:syncfusion_flutter_charts/charts.dart';
import '../providers/tasks_provider.dart';
class WorkoutView extends StatelessWidget {
const WorkoutView({super.key});
@override
Widget build(BuildContext context) {
var tasksModel = context.watch<TasksProvider>();
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SfCartesianChart(
primaryXAxis: DateTimeAxis(),
series: <ChartSeries>[
LineSeries<Workout, DateTime>(
dataSource: tasksModel.workoutHistory,
xValueMapper: (Workout value, _) => value.date,
yValueMapper: (Workout value, _) => value.motivationBefore),
LineSeries<Workout, DateTime>(
dataSource: tasksModel.workoutHistory,
xValueMapper: (Workout value, _) => value.date,
yValueMapper: (Workout value, _) => value.motivationAfter)
],
),
],
);
}
}

View File

@ -205,6 +205,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.2"
intl:
dependency: transitive
description:
name: intl
url: "https://pub.dartlang.org"
source: hosted
version: "0.18.0"
js:
dependency: transitive
description:
@ -441,6 +448,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.1"
syncfusion_flutter_charts:
dependency: "direct main"
description:
name: syncfusion_flutter_charts
url: "https://pub.dartlang.org"
source: hosted
version: "20.4.52"
syncfusion_flutter_core:
dependency: transitive
description:
name: syncfusion_flutter_core
url: "https://pub.dartlang.org"
source: hosted
version: "20.4.52"
synchronized:
dependency: transitive
description:

View File

@ -42,6 +42,7 @@ dependencies:
mobile_scanner: ^3.0.0
flutter_local_notifications: ^13.0.0
http: ^0.13.5
syncfusion_flutter_charts: ^20.4.52
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.