added editing entries and deleting them
This commit is contained in:
parent
fa9a0569fb
commit
955c506b92
9 changed files with 369 additions and 101 deletions
|
@ -76,7 +76,7 @@ class ApiClient {
|
|||
}
|
||||
|
||||
|
||||
Future<Map<String, dynamic>> delete(String path) async {
|
||||
Future<void> delete(String path) async {
|
||||
final response = await httpClient.delete(
|
||||
Uri.parse('$baseUrl$path'),
|
||||
headers: headers(),
|
||||
|
@ -85,8 +85,6 @@ class ApiClient {
|
|||
if (response.statusCode != 200) {
|
||||
throw Exception('Response returned status code: ${response.statusCode}');
|
||||
}
|
||||
|
||||
return _jsonDecode(response);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> patch(String path, Map<String, dynamic> body) async {
|
||||
|
@ -182,4 +180,22 @@ class ApiClient {
|
|||
};
|
||||
await post("/entry", entry);
|
||||
}
|
||||
|
||||
Future<void> deleteEntry(int id) async {
|
||||
await delete("/entry/$id");
|
||||
}
|
||||
|
||||
Future<void> updateEntry(int id, {
|
||||
required double grams,
|
||||
required int productId,
|
||||
required int mealId,
|
||||
}
|
||||
) async {
|
||||
var entry = {
|
||||
"grams": grams,
|
||||
"product_id": productId,
|
||||
"meal_id": mealId,
|
||||
};
|
||||
await patch("/entry/$id", entry);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,7 +65,6 @@ class _AddEntryScreen extends State<AddEntryScreen> {
|
|||
try {
|
||||
double.parse(gramsController.text);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
showError("Grams must be a number");
|
||||
return;
|
||||
}
|
||||
|
|
146
lib/screens/edit_entry.dart
Normal file
146
lib/screens/edit_entry.dart
Normal file
|
@ -0,0 +1,146 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:fooder_web/screens/based.dart';
|
||||
import 'package:fooder_web/models/product.dart';
|
||||
import 'package:fooder_web/models/entry.dart';
|
||||
import 'package:fooder_web/widgets/product.dart';
|
||||
|
||||
|
||||
class EditEntryScreen extends BasedScreen {
|
||||
final Entry entry;
|
||||
|
||||
const EditEntryScreen({super.key, required super.apiClient, required this.entry});
|
||||
|
||||
@override
|
||||
State<EditEntryScreen> createState() => _EditEntryScreen();
|
||||
}
|
||||
|
||||
|
||||
class _EditEntryScreen extends State<EditEntryScreen> {
|
||||
final gramsController = TextEditingController();
|
||||
final productNameController = TextEditingController();
|
||||
List<Product> products = [];
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
gramsController.dispose();
|
||||
productNameController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void popMeDady() {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState () {
|
||||
super.initState();
|
||||
setState(() {
|
||||
gramsController.text = widget.entry.grams.toString();
|
||||
productNameController.text = widget.entry.product.name;
|
||||
products = [widget.entry.product];
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _getProducts() async {
|
||||
var productsMap = await widget.apiClient.getProducts(productNameController.text);
|
||||
setState(() {
|
||||
products = (productsMap['products'] as List<dynamic>).map((e) => Product.fromJson(e as Map<String, dynamic>)).toList();
|
||||
});
|
||||
}
|
||||
|
||||
void showError(String message)
|
||||
{
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(message, textAlign: TextAlign.center),
|
||||
backgroundColor: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _saveEntry() async {
|
||||
if (products.length != 1) {
|
||||
showError("Pick product first");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
double.parse(gramsController.text);
|
||||
} catch (e) {
|
||||
showError("Grams must be a number");
|
||||
return;
|
||||
}
|
||||
|
||||
await widget.apiClient.updateEntry(
|
||||
widget.entry.id,
|
||||
grams: double.parse(gramsController.text),
|
||||
productId: products[0].id,
|
||||
mealId: widget.entry.mealId,
|
||||
);
|
||||
popMeDady();
|
||||
}
|
||||
|
||||
Future<void> _deleteEntry() async {
|
||||
await widget.apiClient.deleteEntry(widget.entry.id);
|
||||
popMeDady();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
||||
title: const Text("🅵🅾🅾🅳🅴🆁"),
|
||||
),
|
||||
body: Center(
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 720),
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: ListView(
|
||||
children: <Widget>[
|
||||
TextFormField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Grams',
|
||||
),
|
||||
controller: gramsController,
|
||||
),
|
||||
TextFormField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Product name',
|
||||
),
|
||||
controller: productNameController,
|
||||
onChanged: (_) => _getProducts(),
|
||||
),
|
||||
for (var product in products)
|
||||
ListTile(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
products = [product];
|
||||
});
|
||||
},
|
||||
title: ProductWidget(
|
||||
product: product,
|
||||
),
|
||||
),
|
||||
]
|
||||
)
|
||||
),
|
||||
),
|
||||
floatingActionButton: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
FloatingActionButton(
|
||||
onPressed: _deleteEntry,
|
||||
heroTag: null,
|
||||
child: const Icon(Icons.delete),
|
||||
),
|
||||
FloatingActionButton(
|
||||
onPressed: _saveEntry,
|
||||
heroTag: null,
|
||||
child: const Icon(Icons.save),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -69,7 +69,7 @@ class _MainScreen extends State<MainScreen> {
|
|||
content = Container(
|
||||
constraints: const BoxConstraints(maxWidth: 720),
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: DiaryWidget(diary: diary!),
|
||||
child: DiaryWidget(diary: diary!, apiClient: widget.apiClient, refreshParent: _asyncInitState),
|
||||
);
|
||||
title = Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
|
|
|
@ -1,55 +1,67 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:fooder_web/models/diary.dart';
|
||||
import 'package:fooder_web/widgets/meal.dart';
|
||||
import 'package:fooder_web/widgets/macro.dart';
|
||||
import 'package:fooder_web/client.dart';
|
||||
import 'dart:core';
|
||||
|
||||
|
||||
class DiaryWidget extends StatelessWidget {
|
||||
final Diary diary;
|
||||
final ApiClient apiClient;
|
||||
final Function() refreshParent;
|
||||
|
||||
const DiaryWidget({super.key, required this.diary});
|
||||
const DiaryWidget({super.key, required this.diary, required this.apiClient, required this.refreshParent});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
SliverAppBar(
|
||||
title: Row(
|
||||
children: <Widget>[
|
||||
const Spacer(),
|
||||
Text(
|
||||
"${diary.calories.toStringAsFixed(1)} kcal",
|
||||
style: Theme.of(context).textTheme.headlineLarge!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSecondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
]
|
||||
),
|
||||
expandedHeight: 128,
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
floating: true,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
title: MacroWidget(
|
||||
protein: diary.protein,
|
||||
carb: diary.carb,
|
||||
fat: diary.fat,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSecondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverList(
|
||||
delegate: SliverChildListDelegate(
|
||||
[
|
||||
for (var meal in diary.meals)
|
||||
MealWidget(
|
||||
meal: meal,
|
||||
),
|
||||
Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
"carb: ${diary.carb.toStringAsFixed(2)}",
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.secondary),
|
||||
),
|
||||
Text(
|
||||
"fat: ${diary.fat.toStringAsFixed(2)}",
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.secondary),
|
||||
),
|
||||
Text(
|
||||
"protein: ${diary.protein.toStringAsFixed(2)}",
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.secondary),
|
||||
),
|
||||
Text(
|
||||
"calories: ${diary.calories.toStringAsFixed(2)}",
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||
apiClient: apiClient,
|
||||
refreshParent: refreshParent,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:fooder_web/models/entry.dart';
|
||||
import 'package:fooder_web/widgets/macro.dart';
|
||||
import 'dart:core';
|
||||
|
||||
|
||||
|
@ -22,29 +23,17 @@ class EntryWidget extends StatelessWidget {
|
|||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
Text("${entry.calories.toStringAsFixed(2)} kcal"),
|
||||
Text("${entry.calories.toStringAsFixed(1)} kcal"),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
"carb: ${entry.carb.toStringAsFixed(2)}",
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.secondary),
|
||||
MacroWidget(
|
||||
protein: entry.protein,
|
||||
carb: entry.carb,
|
||||
fat: entry.fat,
|
||||
amount: entry.grams,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
Text(
|
||||
"fat: ${entry.fat.toStringAsFixed(2)}",
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.secondary),
|
||||
),
|
||||
Text(
|
||||
"protein: ${entry.protein.toStringAsFixed(2)}",
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.secondary),
|
||||
),
|
||||
Text(
|
||||
"amount: ${entry.grams.toStringAsFixed(2)}",
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.secondary),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
102
lib/widgets/macro.dart
Normal file
102
lib/widgets/macro.dart
Normal file
|
@ -0,0 +1,102 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
class MacroWidget extends StatelessWidget {
|
||||
final double? amount;
|
||||
final double? calories;
|
||||
final double protein;
|
||||
final double carb;
|
||||
final double fat;
|
||||
final TextStyle style;
|
||||
|
||||
const MacroWidget({
|
||||
Key? key,
|
||||
this.calories,
|
||||
this.amount,
|
||||
required this.protein,
|
||||
required this.carb,
|
||||
required this.fat,
|
||||
required this.style,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var elements = <Widget>[
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
"C: ${carb.toStringAsFixed(1)} g",
|
||||
style: style,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
"F: ${fat.toStringAsFixed(1)} g",
|
||||
style: style,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
"P: ${protein.toStringAsFixed(1)} g",
|
||||
style: style,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
if (calories != null) {
|
||||
elements.add(
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
"${calories!.toStringAsFixed(1)} kcal",
|
||||
style: style,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (amount != null) {
|
||||
elements.add(
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
"${amount!.toStringAsFixed(1)} g",
|
||||
style: style,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (amount == null && calories == null) {
|
||||
elements.add(
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
"",
|
||||
style: style,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 4.0,
|
||||
bottom: 4.0,
|
||||
left: 8.0,
|
||||
right: 8.0,
|
||||
),
|
||||
child: Row(
|
||||
children: elements,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,13 +1,18 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:fooder_web/models/meal.dart';
|
||||
import 'package:fooder_web/widgets/entry.dart';
|
||||
import 'package:fooder_web/widgets/macro.dart';
|
||||
import 'package:fooder_web/screens/edit_entry.dart';
|
||||
import 'package:fooder_web/client.dart';
|
||||
import 'dart:core';
|
||||
|
||||
|
||||
class MealWidget extends StatelessWidget {
|
||||
final Meal meal;
|
||||
final ApiClient apiClient;
|
||||
final Function() refreshParent;
|
||||
|
||||
const MealWidget({super.key, required this.meal});
|
||||
const MealWidget({super.key, required this.meal, required this.apiClient, required this.refreshParent});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -26,33 +31,39 @@ class MealWidget extends StatelessWidget {
|
|||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
Text("${meal.calories.toStringAsFixed(2)} kcal"),
|
||||
Text("${meal.calories.toStringAsFixed(1)} kcal"),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
"carb: ${meal.carb.toStringAsFixed(2)}",
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.secondary),
|
||||
MacroWidget(
|
||||
protein: meal.protein,
|
||||
carb: meal.carb,
|
||||
fat: meal.fat,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
Text(
|
||||
"fat: ${meal.fat.toStringAsFixed(2)}",
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.secondary),
|
||||
),
|
||||
Text(
|
||||
"protein: ${meal.protein.toStringAsFixed(2)}",
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.secondary),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
children: <Widget>[
|
||||
for (var entry in meal.entries)
|
||||
EntryWidget(
|
||||
ListTile(
|
||||
title: EntryWidget(
|
||||
entry: entry,
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => EditEntryScreen(
|
||||
apiClient: apiClient,
|
||||
entry: entry,
|
||||
),
|
||||
),
|
||||
).then((_) {
|
||||
refreshParent();
|
||||
});
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:fooder_web/models/product.dart';
|
||||
import 'package:fooder_web/widgets/macro.dart';
|
||||
import 'dart:core';
|
||||
|
||||
|
||||
|
@ -22,25 +23,17 @@ class ProductWidget extends StatelessWidget {
|
|||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
Text("${product.calories.toStringAsFixed(2)} kcal"),
|
||||
Text("${product.calories.toStringAsFixed(1)} kcal"),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
"carb: ${product.carb.toStringAsFixed(2)}",
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.secondary),
|
||||
MacroWidget(
|
||||
protein: product.protein,
|
||||
carb: product.carb,
|
||||
fat: product.fat,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
Text(
|
||||
"fat: ${product.fat.toStringAsFixed(2)}",
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.secondary),
|
||||
),
|
||||
Text(
|
||||
"protein: ${product.protein.toStringAsFixed(2)}",
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.secondary),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
Loading…
Reference in a new issue