diff --git a/lib/locale/locales.dart b/lib/locale/locales.dart index c22a34c1..4fb83b14 100644 --- a/lib/locale/locales.dart +++ b/lib/locale/locales.dart @@ -27,20 +27,52 @@ class AppLocalizations { String get newWorkout { return Intl.message( - 'new Workout', + 'New Workout', name: 'newWorkout', desc: 'Header when adding a new workout', ); } + String get newDay { + return Intl.message( + 'New Day', + name: 'newDay', + desc: 'Header when adding a new day to a workout', + ); + } + String get description { return Intl.message( - 'description', + 'Description', name: 'description', desc: 'Description of a workout, nutritional plan, etc.', ); } + String get save { + return Intl.message( + 'Save', + name: 'save', + desc: 'Saving a new entry in the DB', + ); + } + + String get cancel { + return Intl.message( + 'Cancel', + name: 'cancel', + desc: 'Cancelling an action', + ); + } + + String get add { + return Intl.message( + 'Add', + name: 'add', + desc: 'Label for a button etc.', + ); + } + String get labelWorkoutPlan { return Intl.message( 'Workout plan', diff --git a/lib/models/body_weight/weight_entry.g.dart b/lib/models/body_weight/weight_entry.g.dart index 52af3236..7b756212 100644 --- a/lib/models/body_weight/weight_entry.g.dart +++ b/lib/models/body_weight/weight_entry.g.dart @@ -15,8 +15,9 @@ WeightEntry _$WeightEntryFromJson(Map json) { ); } -Map _$WeightEntryToJson(WeightEntry instance) => { +Map _$WeightEntryToJson(WeightEntry instance) => + { 'id': instance.id, - 'weight': instance.weight, + 'weight': toString(instance.weight), 'date': instance.date?.toIso8601String(), }; diff --git a/lib/models/workouts/day.dart b/lib/models/workouts/day.dart index 64bcce76..be349e5d 100644 --- a/lib/models/workouts/day.dart +++ b/lib/models/workouts/day.dart @@ -1,5 +1,7 @@ +import 'package:flutter/cupertino.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:wger/models/workouts/set.dart'; +import 'package:wger/models/workouts/workout_plan.dart'; part 'day.g.dart'; @@ -11,30 +13,36 @@ class Day { @JsonKey(required: true) final String description; - @JsonKey(required: true, name: 'days_of_week') + @JsonKey(required: false, name: 'day') List daysOfWeek = []; - @JsonKey(required: false) + //@JsonKey(required: false) List sets = []; + @JsonKey(required: true, name: 'training') + int workoutId; + + //@JsonKey(required: true, name: 'training') + WorkoutPlan workout; + Day({ this.id, - this.description, + @required this.description, this.daysOfWeek, this.sets, }); - String getDayName(int weekDay) { - Map weekdays = { - 1: 'Monday', - 2: 'Tuesday', - 3: 'Wednesday', - 4: 'Thursday', - 5: 'Friday', - 6: 'Saturday', - 7: 'Sunday', - }; + static const Map weekdays = { + 1: 'Monday', + 2: 'Tuesday', + 3: 'Wednesday', + 4: 'Thursday', + 5: 'Friday', + 6: 'Saturday', + 7: 'Sunday', + }; + String getDayName(int weekDay) { return weekdays[weekDay]; } diff --git a/lib/models/workouts/day.g.dart b/lib/models/workouts/day.g.dart index 0950889d..75bdfaa7 100644 --- a/lib/models/workouts/day.g.dart +++ b/lib/models/workouts/day.g.dart @@ -7,20 +7,26 @@ part of 'day.dart'; // ************************************************************************** Day _$DayFromJson(Map json) { - $checkKeys(json, requiredKeys: const ['id', 'description', 'days_of_week']); + $checkKeys(json, requiredKeys: const ['id', 'description', 'training']); return Day( id: json['id'] as int, description: json['description'] as String, - daysOfWeek: (json['days_of_week'] as List)?.map((e) => e as int)?.toList(), + daysOfWeek: (json['day'] as List)?.map((e) => e as int)?.toList(), sets: (json['sets'] as List) ?.map((e) => e == null ? null : Set.fromJson(e as Map)) ?.toList(), - ); + ) + ..workoutId = json['training'] as int + ..workout = json['workout'] == null + ? null + : WorkoutPlan.fromJson(json['workout'] as Map); } Map _$DayToJson(Day instance) => { 'id': instance.id, 'description': instance.description, - 'days_of_week': instance.daysOfWeek, + 'day': instance.daysOfWeek, 'sets': instance.sets, + 'training': instance.workoutId, + 'workout': instance.workout, }; diff --git a/lib/models/workouts/workout_plan.dart b/lib/models/workouts/workout_plan.dart index 2260f088..704741c0 100644 --- a/lib/models/workouts/workout_plan.dart +++ b/lib/models/workouts/workout_plan.dart @@ -19,8 +19,8 @@ class WorkoutPlan { List days = []; WorkoutPlan({ - @required this.id, - @required this.creationDate, + this.id, + this.creationDate, @required this.description, this.days, }); diff --git a/lib/providers/workout_plans.dart b/lib/providers/workout_plans.dart index 22e91b6c..4918ab36 100644 --- a/lib/providers/workout_plans.dart +++ b/lib/providers/workout_plans.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; @@ -12,8 +13,10 @@ import 'package:wger/providers/auth.dart'; class WorkoutPlans with ChangeNotifier { static const workoutPlansUrl = '/api/v2/workout/'; + static const daysUrl = '/api/v2/day/'; String _url; + String _urlDays; Auth _auth; List _entries = []; @@ -21,6 +24,7 @@ class WorkoutPlans with ChangeNotifier { this._auth = auth; this._entries = entries; this._url = auth.serverUrl + workoutPlansUrl; + this._urlDays = auth.serverUrl + daysUrl; } List get items { @@ -31,6 +35,9 @@ class WorkoutPlans with ChangeNotifier { return _entries.firstWhere((workoutPlan) => workoutPlan.id == id); } + /* + * Workouts + */ Future fetchAndSetWorkouts() async { final response = await http.get( _url, @@ -124,12 +131,12 @@ class WorkoutPlans with ChangeNotifier { // Days days.add(Day( - id: entry['obj']['id'], - description: entry['obj']['description'], - sets: sets, - daysOfWeek: [1, 3], - //daysOfWeek: entry['obj']['day'], - )); + id: entry['obj']['id'], + description: entry['obj']['description'], + sets: sets, + daysOfWeek: [1, 3] + //daysOfWeek: entry['obj']['day'], + )); } workout.days = days; notifyListeners(); @@ -190,4 +197,34 @@ class WorkoutPlans with ChangeNotifier { } existingWorkout = null; } + + /* + * Days + */ + Future addDay(Day day, WorkoutPlan workout) async { + /* + * Saves a new day instance to the DB and adds it to the given workout + */ + day.workoutId = workout.id; + try { + final response = await http.post( + _urlDays, + headers: { + 'Authorization': 'Token ${_auth.token}', + 'Content-Type': 'application/json; charset=UTF-8', + }, + body: json.encode(day.toJson()), + ); + + // Create the day + day = Day.fromJson(json.decode(response.body)); + day.sets = []; + workout.days.insert(0, day); + notifyListeners(); + return day; + } catch (error) { + log(error.missingKeys.toString()); + throw error; + } + } } diff --git a/lib/screens/auth_screen.dart b/lib/screens/auth_screen.dart index a86a11f6..17ebee1b 100644 --- a/lib/screens/auth_screen.dart +++ b/lib/screens/auth_screen.dart @@ -36,9 +36,10 @@ class AuthScreen extends StatelessWidget { height: deviceSize.height, width: deviceSize.width, child: Column( - mainAxisAlignment: MainAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ + Padding(padding: EdgeInsets.symmetric(vertical: 20)), Image( image: AssetImage('assets/images/logo.png'), width: 120, diff --git a/lib/widgets/app_drawer.dart b/lib/widgets/app_drawer.dart index 2fc49ed1..bd01c6d3 100644 --- a/lib/widgets/app_drawer.dart +++ b/lib/widgets/app_drawer.dart @@ -67,9 +67,9 @@ class AppDrawer extends StatelessWidget { leading: Icon(Icons.exit_to_app), title: Text('Logout'), onTap: () { + Provider.of(context, listen: false).logout(); Navigator.of(context).pop(); Navigator.of(context).pushReplacementNamed('/'); - Provider.of(context, listen: false).logout(); }, ), ], diff --git a/lib/widgets/workouts/workout_plan_detail.dart b/lib/widgets/workouts/workout_plan_detail.dart index f24e8fb9..2511f56d 100644 --- a/lib/widgets/workouts/workout_plan_detail.dart +++ b/lib/widgets/workouts/workout_plan_detail.dart @@ -1,46 +1,212 @@ +/* + * This file is part of wger Workout Manager. + * + * wger Workout Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * wger Workout Manager is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + */ + import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; +import 'package:wger/locale/locales.dart'; import 'package:wger/models/workouts/day.dart'; import 'package:wger/models/workouts/workout_plan.dart'; +import 'package:wger/providers/workout_plans.dart'; import 'package:wger/widgets/workouts/day.dart'; -class WorkoutPlansDetail extends StatelessWidget { +class DayCheckbox extends StatefulWidget { + String name; + + DayCheckbox(this.name); + + @override + _DayCheckboxState createState() => _DayCheckboxState(); +} + +class _DayCheckboxState extends State { + bool _isSelected = false; + @override + Widget build(BuildContext context) { + return CheckboxListTile( + title: Text(widget.name), + value: _isSelected, + onChanged: (bool newValue) { + setState(() { + _isSelected = newValue; + }); + }, + ); + } +} + +class WorkoutPlansDetail extends StatefulWidget { WorkoutPlan _workoutPlan; WorkoutPlansDetail(this._workoutPlan); + @override + _WorkoutPlansDetailState createState() => _WorkoutPlansDetailState(); +} + +class _WorkoutPlansDetailState extends State { + final dayController = TextEditingController(); + + Map _dayData = { + 'description': '', + 'daysOfWeek': [1], + }; + + Widget showDaySheet(BuildContext outerContext, WorkoutPlan workout) { + showModalBottomSheet( + context: outerContext, + builder: (BuildContext context) { + return Container( + child: DayFormWidget( + dayController: dayController, + dayData: _dayData, + workout: workout, + ), + ); + }, + ); + } + @override Widget build(BuildContext context) { return Container( + width: double.infinity, child: Column( children: [ Padding( padding: const EdgeInsets.symmetric(vertical: 10), child: Text( - DateFormat('dd.MM.yyyy').format(_workoutPlan.creationDate).toString(), + DateFormat('dd.MM.yyyy').format(widget._workoutPlan.creationDate).toString(), style: Theme.of(context).textTheme.headline6, ), ), - if (_workoutPlan.description != null) - Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: Text( - _workoutPlan.description, - style: Theme.of(context).textTheme.headline5, - ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Text( + widget._workoutPlan.description, + style: Theme.of(context).textTheme.headline5, ), - _workoutPlan.days != null - ? Expanded( - child: ListView.builder( - itemCount: _workoutPlan.days.length, - itemBuilder: (context, index) { - Day workoutDay = _workoutPlan.days[index]; - return WorkoutDayWidget(workoutDay); - }, - ), - ) - : Text('No days'), + ), + Expanded( + child: ListView.builder( + itemCount: widget._workoutPlan.days.length, + itemBuilder: (context, index) { + Day workoutDay = widget._workoutPlan.days[index]; + return WorkoutDayWidget(workoutDay); + }, + ), + ), + Column( + children: [ + ElevatedButton( + child: Text(AppLocalizations.of(context).add), + onPressed: () { + showDaySheet(context, widget._workoutPlan); + }), + ], + ), ], ), ); } } + +class DayFormWidget extends StatelessWidget { + final WorkoutPlan workout; + + DayFormWidget({ + Key key, + @required this.dayController, + @required Map dayData, + @required this.workout, + }) : _dayData = dayData, + super(key: key); + + final TextEditingController dayController; + final Map _dayData; + final _form = GlobalKey(); + + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.all(20), + child: Form( + key: _form, + child: Column( + children: [ + Text( + AppLocalizations.of(context).newDay, + style: Theme.of(context).textTheme.headline6, + ), + TextFormField( + decoration: InputDecoration(labelText: AppLocalizations.of(context).description), + controller: dayController, + onSaved: (value) { + _dayData['description'] = value; + }, + validator: (value) { + const minLenght = 5; + const maxLenght = 100; + if (value.isEmpty || value.length < minLenght || value.length > maxLenght) { + return 'Please enter between $minLenght and $maxLenght characters.'; + } + return null; + }, + ), + TextFormField( + decoration: InputDecoration(labelText: 'TODO: Checkbox for days'), + enabled: false, + ), + //...Day().weekdays.values.map((dayName) => DayCheckbox(dayName)).toList(), + ElevatedButton( + child: Text(AppLocalizations.of(context).save), + onPressed: () async { + if (!_form.currentState.validate()) { + return; + } + _form.currentState.save(); + + try { + Provider.of(context, listen: false) + .addDay(Day(description: dayController.text, daysOfWeek: [1]), workout); + //dayController.text = ''; + //Navigator.of(context).pop(); + } catch (error) { + await showDialog( + context: context, + builder: (ctx) => AlertDialog( + title: Text('An error occurred!'), + content: Text('Something went wrong.'), + actions: [ + FlatButton( + child: Text('Okay'), + onPressed: () { + Navigator.of(ctx).pop(); + }, + ) + ], + ), + ); + } + //dayController.text = ''; + //Navigator.of(context).pop(); + }, + ), + ], + ), + ), + ); + } +}