diff --git a/lib/providers/exercises.dart b/lib/providers/exercises.dart index cd330115..60ec8b0b 100644 --- a/lib/providers/exercises.dart +++ b/lib/providers/exercises.dart @@ -57,7 +57,10 @@ class Exercises extends WgerBaseProvider with ChangeNotifier { } Exercise findById(int id) { - return _exercises.firstWhere((exercise) => exercise.id == id); + return _exercises.firstWhere((exercise) => exercise.id == id, orElse: () { + log('Could not find exercise with ID $id'); + return null; + }); } Future fetchAndSetCategories() async { @@ -118,7 +121,7 @@ class Exercises extends WgerBaseProvider with ChangeNotifier { final response = await client.get(makeUrl( _exercisesUrlPath, - query: {'language': '1', 'limit': '1000'}, // TODO: read the language ID from the locale + query: {'limit': '1000'}, )); final extractedData = json.decode(response.body) as Map; @@ -147,7 +150,7 @@ class Exercises extends WgerBaseProvider with ChangeNotifier { /// /// We could do this locally, but the server has better text searching capabilities /// with postgresql. - Future searchIngredient(String name) async { + Future searchExercise(String name) async { // Send the request final response = await client.get( makeUrl(_exerciseSearchPath, query: {'term': name}), @@ -163,6 +166,10 @@ class Exercises extends WgerBaseProvider with ChangeNotifier { } // Process the response - return json.decode(utf8.decode(response.bodyBytes))['suggestions'] as List; + final result = json.decode(utf8.decode(response.bodyBytes))['suggestions'] as List; + for (var entry in result) { + entry['exercise_obj'] = findById(entry['data']['id']); + } + return result; } } diff --git a/lib/widgets/workouts/exercises.dart b/lib/widgets/workouts/exercises.dart new file mode 100644 index 00000000..ff96a171 --- /dev/null +++ b/lib/widgets/workouts/exercises.dart @@ -0,0 +1,42 @@ +/* + * This file is part of wger Workout Manager . + * Copyright (C) 2020 wger Team + * + * 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 + * along with this program. If not, see . + */ + +import 'package:flutter/material.dart'; + +class ExerciseImage extends StatelessWidget { + const ExerciseImage({ + Key key, + @required this.imageUrl, + }) : super(key: key); + + final String imageUrl; + + @override + Widget build(BuildContext context) { + return imageUrl != null + ? FadeInImage( + placeholder: AssetImage('assets/images/placeholder.png'), + image: NetworkImage(imageUrl), + fit: BoxFit.cover, + ) + : Image( + image: AssetImage('assets/images/placeholder.png'), + color: Color.fromRGBO(255, 255, 255, 0.3), + colorBlendMode: BlendMode.modulate); + } +} diff --git a/lib/widgets/workouts/forms.dart b/lib/widgets/workouts/forms.dart index 2eba7f73..88268c37 100644 --- a/lib/widgets/workouts/forms.dart +++ b/lib/widgets/workouts/forms.dart @@ -20,13 +20,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:provider/provider.dart'; import 'package:wger/locale/locales.dart'; +import 'package:wger/models/exercises/exercise.dart'; import 'package:wger/models/workouts/day.dart'; import 'package:wger/models/workouts/workout_plan.dart'; import 'package:wger/providers/auth.dart'; import 'package:wger/providers/exercises.dart'; import 'package:wger/providers/workout_plans.dart'; +import 'package:wger/widgets/workouts/exercises.dart'; -class DayFormWidget extends StatelessWidget { +class DayFormWidget extends StatefulWidget { final WorkoutPlan workout; DayFormWidget({ @@ -39,6 +41,12 @@ class DayFormWidget extends StatelessWidget { final TextEditingController dayController; final Map _dayData; + + @override + _DayFormWidgetState createState() => _DayFormWidgetState(); +} + +class _DayFormWidgetState extends State { final _form = GlobalKey(); @override @@ -55,9 +63,9 @@ class DayFormWidget extends StatelessWidget { ), TextFormField( decoration: InputDecoration(labelText: AppLocalizations.of(context).description), - controller: dayController, + controller: widget.dayController, onSaved: (value) { - _dayData['description'] = value; + widget._dayData['description'] = value; }, validator: (value) { const minLenght = 5; @@ -82,9 +90,9 @@ class DayFormWidget extends StatelessWidget { _form.currentState.save(); try { - Provider.of(context, listen: false) - .addDay(Day(description: dayController.text, daysOfWeek: [1]), workout); - dayController.clear(); + Provider.of(context, listen: false).addDay( + Day(description: widget.dayController.text, daysOfWeek: [1]), widget.workout); + widget.dayController.clear(); Navigator.of(context).pop(); } catch (error) { await showDialog( @@ -123,6 +131,7 @@ class SetFormWidget extends StatefulWidget { class _SetFormWidgetState extends State { double _currentSetSliderValue = 4; + List _exercises = []; // Form stuff final GlobalKey _formKey = GlobalKey(); @@ -141,27 +150,16 @@ class _SetFormWidgetState extends State { decoration: InputDecoration(labelText: AppLocalizations.of(context).ingredient), ), suggestionsCallback: (pattern) async { - return await Provider.of(context, listen: false) - .searchIngredient(pattern); + return await Provider.of(context, listen: false).searchExercise(pattern); }, itemBuilder: (context, suggestion) { + // TODO: this won't work if the server uses e.g. AWS to serve + // the static files String serverUrl = Provider.of(context, listen: false).serverUrl; - print(suggestion); - print(serverUrl); - print('$serverUrl${suggestion["data"]["image"]}'); return ListTile( leading: Container( width: 45, - child: suggestion['data']['image'] != null - ? FadeInImage( - placeholder: AssetImage('assets/images/placeholder.png'), - image: NetworkImage(serverUrl + suggestion['data']['image']), - fit: BoxFit.cover, - ) - : Image( - image: AssetImage('assets/images/placeholder.png'), - color: Color.fromRGBO(255, 255, 255, 0.3), - colorBlendMode: BlendMode.modulate), + child: ExerciseImage(imageUrl: suggestion['data']['image']), ), title: Text(suggestion['value']), subtitle: Text(suggestion['data']['id'].toString()), @@ -171,20 +169,21 @@ class _SetFormWidgetState extends State { return suggestionsBox; }, onSuggestionSelected: (suggestion) { - print(suggestion); - //mealItem.ingredientId = suggestion['data']['id']; - //this._ingredientController.text = suggestion['value']; + final e = Provider.of(context, listen: false) + .findById(suggestion['data']['id']); + setState(() { + _exercises.add(e); + }); }, validator: (value) { if (value.isEmpty) { return 'Please select an ingredient'; } - //if (mealItem.ingredientId == null) { - // return 'Please select an ingredient'; - //} return null; }, ), + Text('You can search for more than one exercise, they will be grouped ' + 'together for a superset.'), Text('Number of sets:'), Slider( value: _currentSetSliderValue, @@ -198,6 +197,10 @@ class _SetFormWidgetState extends State { }); }, ), + Text('If you do the same repetitions for all sets you can just enter ' + 'one value: e.g. for 4 sets just enter one "10" for the repetitions, ' + 'this automatically becomes "4 x 10".'), + ..._exercises.map((e) => ExerciseSet(e, _currentSetSliderValue)).toList(), ElevatedButton(child: Text('Save'), onPressed: () {}), ], ), @@ -205,3 +208,44 @@ class _SetFormWidgetState extends State { ); } } + +class ExerciseSet extends StatelessWidget { + Exercise _exercise; + int _numberOfSets; + + ExerciseSet(Exercise exercise, double sliderValue) { + this._exercise = exercise; + this._numberOfSets = sliderValue.round(); + } + + Widget getSets() { + List out = []; + for (var i = 1; i <= _numberOfSets; i++) { + out.add(Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Reps'), + Text('Unit'), + Text('Weight'), + Text('RiR'), + ], + )); + } + return Column( + children: out, + ); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Text(_exercise.name, style: TextStyle(fontWeight: FontWeight.bold)), + //ExerciseImage(imageUrl: _exercise.images.first.url), + Divider(), + getSets(), + Padding(padding: EdgeInsets.symmetric(vertical: 5)) + ], + ); + } +}