mirror of
https://github.com/wger-project/flutter.git
synced 2026-02-18 00:17:48 +01:00
Add provider and screen for body weight entries
This commit is contained in:
7
lib/helpers/json.dart
Normal file
7
lib/helpers/json.dart
Normal file
@@ -0,0 +1,7 @@
|
||||
num toNum(String e) {
|
||||
return num.parse(e);
|
||||
}
|
||||
|
||||
String toString(num e) {
|
||||
return e.toString();
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
27
lib/models/body_weight/weight_entry.dart
Normal file
27
lib/models/body_weight/weight_entry.dart
Normal 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);
|
||||
}
|
||||
22
lib/models/body_weight/weight_entry.g.dart
Normal file
22
lib/models/body_weight/weight_entry.g.dart
Normal 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(),
|
||||
};
|
||||
86
lib/providers/body_weight.dart
Normal file
86
lib/providers/body_weight.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
62
lib/widgets/weight/entries_list.dart
Normal file
62
lib/widgets/weight/entries_list.dart
Normal 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'),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user