[preset] GO
This commit is contained in:
parent
5a893f80f0
commit
c443901714
5 changed files with 286 additions and 10 deletions
|
@ -2,6 +2,7 @@ import 'package:http/http.dart' as http;
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:html';
|
import 'dart:html';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:fooder/models/meal.dart';
|
||||||
|
|
||||||
class ApiClient {
|
class ApiClient {
|
||||||
final String baseUrl;
|
final String baseUrl;
|
||||||
|
@ -86,6 +87,24 @@ class ApiClient {
|
||||||
return _jsonDecode(response);
|
return _jsonDecode(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> postNoResult(String path, Map<String, dynamic> body,
|
||||||
|
{bool forLogin = false, bool empty = false}) async {
|
||||||
|
final response = await httpClient.post(
|
||||||
|
Uri.parse('$baseUrl$path'),
|
||||||
|
body: jsonEncode(body),
|
||||||
|
headers: headers(forLogin: forLogin),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 401) {
|
||||||
|
await refresh();
|
||||||
|
return await postNoResult(path, body, forLogin: forLogin);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
throw Exception('Response returned status code: ${response.statusCode}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> delete(String path) async {
|
Future<void> delete(String path) async {
|
||||||
final response = await httpClient.delete(
|
final response = await httpClient.delete(
|
||||||
Uri.parse('$baseUrl$path'),
|
Uri.parse('$baseUrl$path'),
|
||||||
|
@ -187,6 +206,11 @@ class ApiClient {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> getPresets(String? q) async {
|
||||||
|
var response = await get("/preset?${Uri(queryParameters: {"q": q}).query}");
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> addEntry({
|
Future<void> addEntry({
|
||||||
required double grams,
|
required double grams,
|
||||||
required int productId,
|
required int productId,
|
||||||
|
@ -204,6 +228,14 @@ class ApiClient {
|
||||||
await delete("/entry/$id");
|
await delete("/entry/$id");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> deleteMeal(int id) async {
|
||||||
|
await delete("/meal/$id");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deletePreset(int id) async {
|
||||||
|
await delete("/preset/$id");
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> updateEntry(
|
Future<void> updateEntry(
|
||||||
int id, {
|
int id, {
|
||||||
required double grams,
|
required double grams,
|
||||||
|
@ -242,6 +274,16 @@ class ApiClient {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> addMealFromPreset(
|
||||||
|
{required String name, required int diaryId, required int order, required int presetId}) async {
|
||||||
|
await post("/meal/from_preset", {
|
||||||
|
"name": name,
|
||||||
|
"diary_id": diaryId,
|
||||||
|
"order": order,
|
||||||
|
"preset_id": presetId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> addProduct({
|
Future<Map<String, dynamic>> addProduct({
|
||||||
required String name,
|
required String name,
|
||||||
required double protein,
|
required double protein,
|
||||||
|
@ -258,4 +300,10 @@ class ApiClient {
|
||||||
});
|
});
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> saveMeal(Meal meal, String name) async {
|
||||||
|
await postNoResult("/meal/${meal.id}/save", {
|
||||||
|
"name": name,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
28
lib/models/preset.dart
Normal file
28
lib/models/preset.dart
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
class Preset {
|
||||||
|
final int id;
|
||||||
|
final String name;
|
||||||
|
final double calories;
|
||||||
|
final double protein;
|
||||||
|
final double carb;
|
||||||
|
final double fat;
|
||||||
|
final double fiber;
|
||||||
|
|
||||||
|
Preset({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.calories,
|
||||||
|
required this.protein,
|
||||||
|
required this.carb,
|
||||||
|
required this.fat,
|
||||||
|
required this.fiber,
|
||||||
|
});
|
||||||
|
|
||||||
|
Preset.fromJson(Map<String, dynamic> map)
|
||||||
|
: id = map['id'] as int,
|
||||||
|
name = map['name'] as String,
|
||||||
|
calories = map['calories'] as double,
|
||||||
|
protein = map['protein'] as double,
|
||||||
|
carb = map['carb'] as double,
|
||||||
|
fat = map['fat'] as double,
|
||||||
|
fiber = map['fiber'] as double;
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:fooder/screens/based.dart';
|
import 'package:fooder/screens/based.dart';
|
||||||
import 'package:fooder/models/diary.dart';
|
import 'package:fooder/models/diary.dart';
|
||||||
|
import 'package:fooder/models/preset.dart';
|
||||||
|
import 'package:fooder/widgets/preset.dart';
|
||||||
|
|
||||||
class AddMealScreen extends BasedScreen {
|
class AddMealScreen extends BasedScreen {
|
||||||
final Diary diary;
|
final Diary diary;
|
||||||
|
@ -14,6 +16,20 @@ class AddMealScreen extends BasedScreen {
|
||||||
|
|
||||||
class _AddMealScreen extends State<AddMealScreen> {
|
class _AddMealScreen extends State<AddMealScreen> {
|
||||||
final nameController = TextEditingController();
|
final nameController = TextEditingController();
|
||||||
|
final presetNameController = TextEditingController();
|
||||||
|
bool nameChanged = false;
|
||||||
|
List<Preset> presets = [];
|
||||||
|
|
||||||
|
Future<void> _getPresets() async {
|
||||||
|
var presetsMap =
|
||||||
|
await widget.apiClient.getPresets(presetNameController.text);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
presets = (presetsMap['presets'] as List<dynamic>)
|
||||||
|
.map((e) => Preset.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -21,6 +37,7 @@ class _AddMealScreen extends State<AddMealScreen> {
|
||||||
setState(() {
|
setState(() {
|
||||||
nameController.text = "Meal ${widget.diary.meals.length}";
|
nameController.text = "Meal ${widget.diary.meals.length}";
|
||||||
});
|
});
|
||||||
|
_getPresets();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -53,26 +70,98 @@ class _AddMealScreen extends State<AddMealScreen> {
|
||||||
popMeDaddy();
|
popMeDaddy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _deletePreset(context, Preset preset) async {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('Confirm deletion of the preset'),
|
||||||
|
actions: <Widget>[
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.cancel),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.delete),
|
||||||
|
onPressed: () {
|
||||||
|
widget.apiClient.deletePreset(preset.id);
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _addMealFromPreset() async {
|
||||||
|
if (presets.length != 1) {
|
||||||
|
_addMeal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await widget.apiClient.addMealFromPreset(
|
||||||
|
name: nameChanged ? nameController.text : presets[0].name,
|
||||||
|
diaryId: widget.diary.id,
|
||||||
|
order: widget.diary.meals.length,
|
||||||
|
presetId: presets[0].id,
|
||||||
|
);
|
||||||
|
popMeDaddy();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
_getPresets();
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
||||||
title: Text("🅵🅾🅾🅳🅴🆁", style: logoStyle(context)),
|
title: Text("🅵🅾🅾🅳🅴🆁", style: logoStyle(context)),
|
||||||
),
|
),
|
||||||
body: Center(
|
body: Center(
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: const BoxConstraints(maxWidth: 720),
|
constraints: const BoxConstraints(maxWidth: 720),
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
child: TextFormField(
|
child: ListView(children: <Widget>[
|
||||||
decoration: const InputDecoration(
|
TextFormField(
|
||||||
labelText: 'Meal name',
|
decoration: const InputDecoration(
|
||||||
),
|
labelText: 'Meal name',
|
||||||
controller: nameController,
|
),
|
||||||
|
controller: nameController,
|
||||||
|
onChanged: (_) => setState(() {
|
||||||
|
nameChanged = true;
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
TextFormField(
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
hintText: 'Search presets',
|
||||||
|
),
|
||||||
|
controller: presetNameController,
|
||||||
|
onChanged: (_) => _getPresets(),
|
||||||
|
),
|
||||||
|
for (var preset in presets)
|
||||||
|
ListTile(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
presets = [preset];
|
||||||
|
presetNameController.text = preset.name;
|
||||||
|
});
|
||||||
|
_addMealFromPreset();
|
||||||
|
},
|
||||||
|
onLongPress: () {
|
||||||
|
_deletePreset(context, preset);
|
||||||
|
},
|
||||||
|
title: PresetWidget(
|
||||||
|
preset: preset,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]),
|
||||||
),
|
),
|
||||||
)),
|
),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
onPressed: _addMeal,
|
onPressed: _addMealFromPreset,
|
||||||
child: const Icon(Icons.add),
|
child: const Icon(Icons.add),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -17,6 +17,69 @@ class MealWidget extends StatelessWidget {
|
||||||
required this.apiClient,
|
required this.apiClient,
|
||||||
required this.refreshParent});
|
required this.refreshParent});
|
||||||
|
|
||||||
|
Future<void> saveMeal(context) async {
|
||||||
|
TextEditingController textFieldController = TextEditingController();
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('Save Meal'),
|
||||||
|
content: TextField(
|
||||||
|
controller: textFieldController,
|
||||||
|
decoration: const InputDecoration(hintText: "Meal template name"),
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.cancel),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.save),
|
||||||
|
onPressed: () {
|
||||||
|
apiClient.saveMeal(meal, textFieldController.text);
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _deleteMeal(Meal meal) async {
|
||||||
|
await apiClient.deleteMeal(meal.id);
|
||||||
|
refreshParent();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteMeal(context) async {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('Confirm deletion of the meal'),
|
||||||
|
actions: <Widget>[
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.cancel),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.delete),
|
||||||
|
onPressed: () {
|
||||||
|
_deleteMeal(meal);
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
return Card(
|
||||||
|
@ -36,6 +99,14 @@ class MealWidget extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.delete),
|
||||||
|
onPressed: () {deleteMeal(context);},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.save),
|
||||||
|
onPressed: () {saveMeal(context);},
|
||||||
|
),
|
||||||
Text("${meal.calories.toStringAsFixed(1)} kcal"),
|
Text("${meal.calories.toStringAsFixed(1)} kcal"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
40
lib/widgets/preset.dart
Normal file
40
lib/widgets/preset.dart
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:fooder/models/preset.dart';
|
||||||
|
import 'package:fooder/widgets/macro.dart';
|
||||||
|
import 'dart:core';
|
||||||
|
|
||||||
|
class PresetWidget extends StatelessWidget {
|
||||||
|
final Preset preset;
|
||||||
|
|
||||||
|
const PresetWidget({super.key, required this.preset});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
preset.name,
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text("${preset.calories.toStringAsFixed(1)} kcal"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
MacroWidget(
|
||||||
|
protein: preset.protein,
|
||||||
|
carb: preset.carb,
|
||||||
|
fat: preset.fat,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue