Add provider and screen for body weight entries

This commit is contained in:
Roland Geider
2020-11-21 11:44:29 +01:00
parent e202eaf1bb
commit 7f8ecfe770
8 changed files with 239 additions and 2 deletions

7
lib/helpers/json.dart Normal file
View File

@@ -0,0 +1,7 @@
num toNum(String e) {
return num.parse(e);
}
String toString(num e) {
return e.toString();
}

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:provider/provider.dart';
import 'package:wger/locale/locales.dart';
import 'package:wger/providers/body_weight.dart';
import 'package:wger/providers/exercises.dart';
import 'package:wger/providers/nutritional_plans.dart';
import 'package:wger/providers/workout_plans.dart';
@@ -53,6 +54,13 @@ class MyApp extends StatelessWidget {
auth,
previous == null ? [] : previous.items,
),
),
ChangeNotifierProxyProvider<Auth, BodyWeight>(
create: null, // TODO: Create is required but it can be null??
update: (context, value, previous) => BodyWeight(
auth,
previous == null ? [] : previous.items,
),
)
],
child: MaterialApp(

View File

@@ -0,0 +1,27 @@
import 'package:flutter/foundation.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:wger/helpers/json.dart';
part 'weight_entry.g.dart';
@JsonSerializable()
class WeightEntry {
@JsonKey(required: true)
final int id;
@JsonKey(required: true, fromJson: toNum, toJson: toString)
final num weight;
@JsonKey(required: true)
final DateTime date;
WeightEntry({
@required this.id,
@required this.weight,
@required this.date,
});
// Boilerplate
factory WeightEntry.fromJson(Map<String, dynamic> json) => _$WeightEntryFromJson(json);
Map<String, dynamic> toJson() => _$WeightEntryToJson(this);
}

View File

@@ -0,0 +1,22 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'weight_entry.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
WeightEntry _$WeightEntryFromJson(Map<String, dynamic> json) {
$checkKeys(json, requiredKeys: const ['id', 'weight', 'date']);
return WeightEntry(
id: json['id'] as int,
weight: toNum(json['weight'] as String),
date: json['date'] == null ? null : DateTime.parse(json['date'] as String),
);
}
Map<String, dynamic> _$WeightEntryToJson(WeightEntry instance) => <String, dynamic>{
'id': instance.id,
'weight': instance.weight,
'date': instance.date?.toIso8601String(),
};

View File

@@ -0,0 +1,86 @@
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:wger/models/body_weight/weight_entry.dart';
import 'package:wger/providers/auth.dart';
class BodyWeight with ChangeNotifier {
static const nutritionPlansUrl = '/api/v2/weightentry/';
String _url;
Auth _auth;
List<WeightEntry> _entries = [];
BodyWeight(Auth auth, List<WeightEntry> entries) {
this._auth = auth;
this._entries = entries;
this._url = auth.serverUrl + nutritionPlansUrl;
}
List<WeightEntry> get items {
return [..._entries];
}
WeightEntry findById(int id) {
return _entries.firstWhere((plan) => plan.id == id);
}
Future<void> fetchAndSetEntries() async {
final response = await http.get(
_url,
headers: <String, String>{'Authorization': 'Token ${_auth.token}'},
);
final extractedData = json.decode(response.body) as Map<String, dynamic>;
final List<WeightEntry> loadedEntries = [];
if (loadedEntries == null) {
return;
}
for (final entry in extractedData['results']) {
loadedEntries.add(WeightEntry.fromJson(entry));
}
_entries = loadedEntries;
notifyListeners();
}
Future<void> addEntry(WeightEntry entry) async {
try {
final response = await http.post(
_url,
headers: {
'Authorization': 'Token ${_auth.token}',
'Content-Type': 'application/json; charset=UTF-8',
},
body: json.encode(entry.toJson()),
);
_entries.insert(0, WeightEntry.fromJson(json.decode(response.body)));
notifyListeners();
} catch (error) {
log(error);
throw error;
}
}
Future<void> deleteEntry(int id) async {
final url = '$_url$id/';
final existingPlanIndex = _entries.indexWhere((element) => element.id == id);
var existingPlan = _entries[existingPlanIndex];
_entries.removeAt(existingPlanIndex);
notifyListeners();
final response = await http.delete(
url,
headers: {'Authorization': 'Token ${_auth.token}'},
);
if (response.statusCode >= 400) {
_entries.insert(existingPlanIndex, existingPlan);
notifyListeners();
//throw HttpException();
}
existingPlan = null;
}
}

View File

@@ -1,9 +1,17 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wger/models/body_weight/weight_entry.dart';
import 'package:wger/providers/body_weight.dart';
import 'package:wger/widgets/app_drawer.dart';
import 'package:wger/widgets/weight/entries_list.dart';
class WeightScreen extends StatelessWidget {
static const routeName = '/weight';
Future<void> _refreshWeightEntries(BuildContext context) async {
await Provider.of<BodyWeight>(context, listen: false).fetchAndSetEntries();
}
Widget getAppBar() {
return AppBar(
title: Text('Weight'),
@@ -22,9 +30,27 @@ class WeightScreen extends StatelessWidget {
appBar: getAppBar(),
drawer: AppDrawer(),
floatingActionButton: FloatingActionButton(
onPressed: () {},
onPressed: () {
Provider.of<BodyWeight>(context, listen: false).addEntry(
WeightEntry(
date: DateTime.now(),
weight: 80,
),
);
},
child: const Icon(Icons.add),
),
body: FutureBuilder(
future: _refreshWeightEntries(context),
builder: (context, snapshot) => snapshot.connectionState == ConnectionState.waiting
? Center(child: CircularProgressIndicator())
: RefreshIndicator(
onRefresh: () => _refreshWeightEntries(context),
child: Consumer<BodyWeight>(
builder: (context, productsData, child) => WeightEntriesList(),
),
),
),
);
}
}

View File

@@ -0,0 +1,62 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:wger/providers/body_weight.dart';
class WeightEntriesList extends StatelessWidget {
@override
Widget build(BuildContext context) {
final weightEntriesData = Provider.of<BodyWeight>(context);
return ListView.builder(
padding: const EdgeInsets.all(10.0),
itemCount: weightEntriesData.items.length,
itemBuilder: (context, index) {
final currentEntry = weightEntriesData.items[index];
return Dismissible(
key: Key(currentEntry.id.toString()),
onDismissed: (direction) {
// Delete workout from DB
Provider.of<BodyWeight>(context, listen: false).deleteEntry(currentEntry.id);
// and inform the user
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(
"Weight entry for the ${currentEntry.date}",
textAlign: TextAlign.center,
),
),
);
},
background: Container(
color: Theme.of(context).errorColor,
alignment: Alignment.centerRight,
padding: EdgeInsets.only(right: 20),
margin: EdgeInsets.symmetric(
horizontal: 4,
vertical: 4,
),
child: Icon(
Icons.delete,
color: Colors.white,
),
),
direction: DismissDirection.endToStart,
child: Card(
child: ListTile(
//onTap: () => Navigator.of(context).pushNamed(
// WorkoutPlanScreen.routeName,
// arguments: currentPlan,
//),
onTap: () {},
title: Text(
DateFormat('dd.MM.yyyy').format(currentEntry.date).toString(),
),
subtitle: Text('${currentEntry.weight} kg'),
),
),
);
},
);
}
}

View File

@@ -49,7 +49,6 @@ class WorkoutPlansList extends StatelessWidget {
WorkoutPlanScreen.routeName,
arguments: currentWorkout,
),
leading: Icon(Icons.edit),
title: Text(
DateFormat('dd.MM.yyyy').format(currentWorkout.creationDate).toString(),
),