This commit is contained in:
doman 2023-08-28 14:45:32 +02:00
parent 6d7613c5ff
commit 072ef2f963
19 changed files with 285 additions and 283 deletions

View file

@ -3,7 +3,6 @@ import 'dart:convert';
import 'dart:html'; import 'dart:html';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
class ApiClient { class ApiClient {
final String baseUrl; final String baseUrl;
String? token; String? token;
@ -67,7 +66,8 @@ class ApiClient {
return _jsonDecode(response); return _jsonDecode(response);
} }
Future<Map<String, dynamic>> post(String path, Map<String, dynamic> body, {bool forLogin = false}) async { Future<Map<String, dynamic>> post(String path, Map<String, dynamic> body,
{bool forLogin = false}) async {
final response = await httpClient.post( final response = await httpClient.post(
Uri.parse('$baseUrl$path'), Uri.parse('$baseUrl$path'),
body: jsonEncode(body), body: jsonEncode(body),
@ -86,7 +86,6 @@ class ApiClient {
return _jsonDecode(response); return _jsonDecode(response);
} }
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'),
@ -103,7 +102,8 @@ class ApiClient {
} }
} }
Future<Map<String, dynamic>> patch(String path, Map<String, dynamic> body) async { Future<Map<String, dynamic>> patch(
String path, Map<String, dynamic> body) async {
final response = await httpClient.patch( final response = await httpClient.patch(
Uri.parse('$baseUrl$path'), Uri.parse('$baseUrl$path'),
body: jsonEncode(body), body: jsonEncode(body),
@ -155,12 +155,9 @@ class ApiClient {
throw Exception("No valid refresh token found"); throw Exception("No valid refresh token found");
} }
final response = await post( final response = await post("/token/refresh", {
"/token/refresh", "refresh_token": refreshToken,
{ });
"refresh_token": refreshToken,
}
);
token = response['access_token'] as String; token = response['access_token'] as String;
window.localStorage['token'] = token!; window.localStorage['token'] = token!;
@ -185,7 +182,8 @@ class ApiClient {
} }
Future<Map<String, dynamic>> getProducts(String q) async { Future<Map<String, dynamic>> getProducts(String q) async {
var response = await get("/product?${Uri(queryParameters: {"q": q}).query}"); var response =
await get("/product?${Uri(queryParameters: {"q": q}).query}");
return response; return response;
} }
@ -193,8 +191,7 @@ class ApiClient {
required double grams, required double grams,
required int productId, required int productId,
required int mealId, required int mealId,
} }) async {
) async {
var entry = { var entry = {
"grams": grams, "grams": grams,
"product_id": productId, "product_id": productId,
@ -207,12 +204,12 @@ class ApiClient {
await delete("/entry/$id"); await delete("/entry/$id");
} }
Future<void> updateEntry(int id, { Future<void> updateEntry(
int id, {
required double grams, required double grams,
required int productId, required int productId,
required int mealId, required int mealId,
} }) async {
) async {
var entry = { var entry = {
"grams": grams, "grams": grams,
"product_id": productId, "product_id": productId,
@ -223,7 +220,9 @@ class ApiClient {
Future<void> register(String username, String password) async { Future<void> register(String username, String password) async {
try { try {
await post("/user", { await post(
"/user",
{
"username": username, "username": username,
"password": password, "password": password,
}, },
@ -234,7 +233,8 @@ class ApiClient {
} }
} }
Future<void> addMeal({required String name, required int diaryId, required int order}) async { Future<void> addMeal(
{required String name, required int diaryId, required int order}) async {
await post("/meal", { await post("/meal", {
"name": name, "name": name,
"diary_id": diaryId, "diary_id": diaryId,

View file

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:fooder/screens/login.dart'; import 'package:fooder/screens/login.dart';
import 'package:fooder/client.dart'; import 'package:fooder/client.dart';
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
const MyApp({super.key}); const MyApp({super.key});
@ -26,7 +25,6 @@ class MyApp extends StatelessWidget {
} }
} }
void main() { void main() {
runApp(const MyApp()); runApp(const MyApp());
} }

View file

@ -1,6 +1,5 @@
import 'package:fooder/models/meal.dart'; import 'package:fooder/models/meal.dart';
class Diary { class Diary {
final int id; final int id;
final DateTime date; final DateTime date;
@ -22,13 +21,15 @@ class Diary {
required this.fiber, required this.fiber,
}); });
Diary.fromJson(Map<String, dynamic> map): Diary.fromJson(Map<String, dynamic> map)
id = map['id'] as int, : id = map['id'] as int,
date = DateTime.parse(map['date']), date = DateTime.parse(map['date']),
meals = (map['meals'] as List<dynamic>).map((e) => Meal.fromJson(e as Map<String, dynamic>)).toList(), meals = (map['meals'] as List<dynamic>)
calories = map['calories'] as double, .map((e) => Meal.fromJson(e as Map<String, dynamic>))
protein = map['protein'] as double, .toList(),
carb = map['carb'] as double, calories = map['calories'] as double,
fat = map['fat'] as double, protein = map['protein'] as double,
fiber = map['fiber'] as double; carb = map['carb'] as double,
fat = map['fat'] as double,
fiber = map['fiber'] as double;
} }

View file

@ -11,7 +11,6 @@ class Entry {
final double fiber; final double fiber;
final double carb; final double carb;
Entry({ Entry({
required this.id, required this.id,
required this.grams, required this.grams,
@ -24,14 +23,14 @@ class Entry {
required this.carb, required this.carb,
}); });
Entry.fromJson(Map<String, dynamic> map): Entry.fromJson(Map<String, dynamic> map)
id = map['id'] as int, : id = map['id'] as int,
grams = map['grams'] as double, grams = map['grams'] as double,
product = Product.fromJson(map['product'] as Map<String, dynamic>), product = Product.fromJson(map['product'] as Map<String, dynamic>),
mealId = map['meal_id'] as int, mealId = map['meal_id'] as int,
calories = map['calories'] as double, calories = map['calories'] as double,
protein = map['protein'] as double, protein = map['protein'] as double,
fat = map['fat'] as double, fat = map['fat'] as double,
fiber = map['fiber'] as double, fiber = map['fiber'] as double,
carb = map['carb'] as double; carb = map['carb'] as double;
} }

View file

@ -1,6 +1,5 @@
import 'package:fooder/models/entry.dart'; import 'package:fooder/models/entry.dart';
class Meal { class Meal {
final List<Entry> entries; final List<Entry> entries;
final int id; final int id;
@ -26,15 +25,17 @@ class Meal {
required this.diaryId, required this.diaryId,
}); });
Meal.fromJson(Map<String, dynamic> map): Meal.fromJson(Map<String, dynamic> map)
entries = (map['entries'] as List<dynamic>).map((e) => Entry.fromJson(e as Map<String, dynamic>)).toList(), : entries = (map['entries'] as List<dynamic>)
id = map['id'] as int, .map((e) => Entry.fromJson(e as Map<String, dynamic>))
name = map['name'] as String, .toList(),
order = map['order'] as int, id = map['id'] as int,
calories = map['calories'] as double, name = map['name'] as String,
protein = map['protein'] as double, order = map['order'] as int,
carb = map['carb'] as double, calories = map['calories'] as double,
fat = map['fat'] as double, protein = map['protein'] as double,
fiber = map['fiber'] as double, carb = map['carb'] as double,
diaryId = map['diary_id'] as int; fat = map['fat'] as double,
fiber = map['fiber'] as double,
diaryId = map['diary_id'] as int;
} }

View file

@ -17,12 +17,12 @@ class Product {
required this.fiber, required this.fiber,
}); });
Product.fromJson(Map<String, dynamic> map): Product.fromJson(Map<String, dynamic> map)
id = map['id'] as int, : id = map['id'] as int,
name = map['name'] as String, name = map['name'] as String,
calories = map['calories'] as double, calories = map['calories'] as double,
protein = map['protein'] as double, protein = map['protein'] as double,
carb = map['carb'] as double, carb = map['carb'] as double,
fat = map['fat'] as double, fat = map['fat'] as double,
fiber = map['fiber'] as double; fiber = map['fiber'] as double;
} }

View file

@ -7,17 +7,16 @@ import 'package:fooder/models/meal.dart';
import 'package:fooder/widgets/product.dart'; import 'package:fooder/widgets/product.dart';
import 'package:fooder/screens/add_product.dart'; import 'package:fooder/screens/add_product.dart';
class AddEntryScreen extends BasedScreen { class AddEntryScreen extends BasedScreen {
final Diary diary; final Diary diary;
const AddEntryScreen({super.key, required super.apiClient, required this.diary}); const AddEntryScreen(
{super.key, required super.apiClient, required this.diary});
@override @override
State<AddEntryScreen> createState() => _AddEntryScreen(); State<AddEntryScreen> createState() => _AddEntryScreen();
} }
class _AddEntryScreen extends State<AddEntryScreen> { class _AddEntryScreen extends State<AddEntryScreen> {
final gramsController = TextEditingController(); final gramsController = TextEditingController();
final productNameController = TextEditingController(); final productNameController = TextEditingController();
@ -38,7 +37,7 @@ class _AddEntryScreen extends State<AddEntryScreen> {
} }
@override @override
void initState () { void initState() {
super.initState(); super.initState();
setState(() { setState(() {
meal = widget.diary.meals[0]; meal = widget.diary.meals[0];
@ -47,14 +46,16 @@ class _AddEntryScreen extends State<AddEntryScreen> {
} }
Future<void> _getProducts() async { Future<void> _getProducts() async {
var productsMap = await widget.apiClient.getProducts(productNameController.text); var productsMap =
await widget.apiClient.getProducts(productNameController.text);
setState(() { setState(() {
products = (productsMap['products'] as List<dynamic>).map((e) => Product.fromJson(e as Map<String, dynamic>)).toList(); products = (productsMap['products'] as List<dynamic>)
.map((e) => Product.fromJson(e as Map<String, dynamic>))
.toList();
}); });
} }
void showError(String message) void showError(String message) {
{
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(message, textAlign: TextAlign.center), content: Text(message, textAlign: TextAlign.center),
@ -104,10 +105,9 @@ class _AddEntryScreen extends State<AddEntryScreen> {
), ),
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: ListView( child: ListView(children: <Widget>[
children: <Widget>[
DropdownButton<Meal>( DropdownButton<Meal>(
value: meal, value: meal,
// Callback that sets the selected popup menu item. // Callback that sets the selected popup menu item.
@ -131,9 +131,11 @@ class _AddEntryScreen extends State<AddEntryScreen> {
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Grams', labelText: 'Grams',
), ),
keyboardType:const TextInputType.numberWithOptions(decimal: true), keyboardType:
const TextInputType.numberWithOptions(decimal: true),
inputFormatters: <TextInputFormatter>[ inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.allow(RegExp(r'^(\d+)?[\.,]?\d{0,2}')), FilteringTextInputFormatter.allow(
RegExp(r'^(\d+)?[\.,]?\d{0,2}')),
], ],
controller: gramsController, controller: gramsController,
onFieldSubmitted: (_) => _addEntry(), onFieldSubmitted: (_) => _addEntry(),
@ -170,20 +172,18 @@ class _AddEntryScreen extends State<AddEntryScreen> {
), ),
for (var product in products) for (var product in products)
ListTile( ListTile(
onTap: () { onTap: () {
setState(() { setState(() {
products = [product]; products = [product];
productNameController.text = product.name; productNameController.text = product.name;
}); });
_addEntry(silent: true); _addEntry(silent: true);
}, },
title: ProductWidget( title: ProductWidget(
product: product, product: product,
),
), ),
), ])),
]
)
),
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
onPressed: _addEntry, onPressed: _addEntry,

View file

@ -2,22 +2,21 @@ 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';
class AddMealScreen extends BasedScreen { class AddMealScreen extends BasedScreen {
final Diary diary; final Diary diary;
const AddMealScreen({super.key, required super.apiClient, required this.diary}); const AddMealScreen(
{super.key, required super.apiClient, required this.diary});
@override @override
State<AddMealScreen> createState() => _AddMealScreen(); State<AddMealScreen> createState() => _AddMealScreen();
} }
class _AddMealScreen extends State<AddMealScreen> { class _AddMealScreen extends State<AddMealScreen> {
final nameController = TextEditingController(); final nameController = TextEditingController();
@override @override
void initState () { void initState() {
super.initState(); super.initState();
setState(() { setState(() {
nameController.text = "Meal ${widget.diary.meals.length}"; nameController.text = "Meal ${widget.diary.meals.length}";
@ -36,8 +35,7 @@ class _AddMealScreen extends State<AddMealScreen> {
); );
} }
void showError(String message) void showError(String message) {
{
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(message, textAlign: TextAlign.center), content: Text(message, textAlign: TextAlign.center),
@ -63,17 +61,16 @@ class _AddMealScreen extends State<AddMealScreen> {
title: const Text("🅵🅾🅾🅳🅴🆁"), title: const Text("🅵🅾🅾🅳🅴🆁"),
), ),
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: TextFormField(
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Meal name', labelText: 'Meal name',
), ),
controller: nameController, controller: nameController,
),
)
), ),
)),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
onPressed: _addMeal, onPressed: _addMeal,
child: const Icon(Icons.add), child: const Icon(Icons.add),

View file

@ -3,7 +3,6 @@ import 'package:flutter/services.dart';
import 'package:fooder/screens/based.dart'; import 'package:fooder/screens/based.dart';
import 'package:fooder/models/product.dart'; import 'package:fooder/models/product.dart';
class AddProductScreen extends BasedScreen { class AddProductScreen extends BasedScreen {
const AddProductScreen({super.key, required super.apiClient}); const AddProductScreen({super.key, required super.apiClient});
@ -11,7 +10,6 @@ class AddProductScreen extends BasedScreen {
State<AddProductScreen> createState() => _AddProductScreen(); State<AddProductScreen> createState() => _AddProductScreen();
} }
class _AddProductScreen extends State<AddProductScreen> { class _AddProductScreen extends State<AddProductScreen> {
final nameController = TextEditingController(); final nameController = TextEditingController();
final carbController = TextEditingController(); final carbController = TextEditingController();
@ -36,8 +34,7 @@ class _AddProductScreen extends State<AddProductScreen> {
); );
} }
void showError(String message) void showError(String message) {
{
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(message, textAlign: TextAlign.center), content: Text(message, textAlign: TextAlign.center),
@ -76,7 +73,8 @@ class _AddProductScreen extends State<AddProductScreen> {
var product = Product.fromJson(productJson); var product = Product.fromJson(productJson);
popMeDaddy(product); popMeDaddy(product);
} catch (e) { } catch (e) {
showError("Error adding product, make sure there is no product with the same name"); showError(
"Error adding product, make sure there is no product with the same name");
return; return;
} }
} }
@ -117,10 +115,9 @@ class _AddProductScreen extends State<AddProductScreen> {
), ),
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: Column( child: Column(children: <Widget>[
children: <Widget>[
TextFormField( TextFormField(
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Product name', labelText: 'Product name',
@ -131,9 +128,11 @@ class _AddProductScreen extends State<AddProductScreen> {
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Carbs', labelText: 'Carbs',
), ),
keyboardType:const TextInputType.numberWithOptions(decimal: true), keyboardType:
const TextInputType.numberWithOptions(decimal: true),
inputFormatters: <TextInputFormatter>[ inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.allow(RegExp(r'^(\d+)?[\.,]?\d{0,2}')), FilteringTextInputFormatter.allow(
RegExp(r'^(\d+)?[\.,]?\d{0,2}')),
], ],
controller: carbController, controller: carbController,
onChanged: (String value) { onChanged: (String value) {
@ -144,9 +143,11 @@ class _AddProductScreen extends State<AddProductScreen> {
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Fat', labelText: 'Fat',
), ),
keyboardType:const TextInputType.numberWithOptions(decimal: true), keyboardType:
const TextInputType.numberWithOptions(decimal: true),
inputFormatters: <TextInputFormatter>[ inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.allow(RegExp(r'^(\d+)?[\.,]?\d{0,2}')), FilteringTextInputFormatter.allow(
RegExp(r'^(\d+)?[\.,]?\d{0,2}')),
], ],
controller: fatController, controller: fatController,
onChanged: (String value) { onChanged: (String value) {
@ -157,9 +158,11 @@ class _AddProductScreen extends State<AddProductScreen> {
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Protein', labelText: 'Protein',
), ),
keyboardType:const TextInputType.numberWithOptions(decimal: true), keyboardType:
const TextInputType.numberWithOptions(decimal: true),
inputFormatters: <TextInputFormatter>[ inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.allow(RegExp(r'^(\d+)?[\.,]?\d{0,2}')), FilteringTextInputFormatter.allow(
RegExp(r'^(\d+)?[\.,]?\d{0,2}')),
], ],
controller: proteinController, controller: proteinController,
onChanged: (String value) { onChanged: (String value) {
@ -170,9 +173,11 @@ class _AddProductScreen extends State<AddProductScreen> {
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Fiber', labelText: 'Fiber',
), ),
keyboardType:const TextInputType.numberWithOptions(decimal: true), keyboardType:
const TextInputType.numberWithOptions(decimal: true),
inputFormatters: <TextInputFormatter>[ inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.allow(RegExp(r'^(\d+)?[\.,]?\d{0,2}')), FilteringTextInputFormatter.allow(
RegExp(r'^(\d+)?[\.,]?\d{0,2}')),
], ],
controller: fiberController, controller: fiberController,
onChanged: (String value) { onChanged: (String value) {
@ -183,9 +188,7 @@ class _AddProductScreen extends State<AddProductScreen> {
"${calculateCalories().toStringAsFixed(2)} kcal", "${calculateCalories().toStringAsFixed(2)} kcal",
textAlign: TextAlign.right, textAlign: TextAlign.right,
), ),
] ])),
)
),
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
onPressed: _addProduct, onPressed: _addProduct,

View file

@ -1,7 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fooder/client.dart'; import 'package:fooder/client.dart';
abstract class BasedScreen extends StatefulWidget { abstract class BasedScreen extends StatefulWidget {
final ApiClient apiClient; final ApiClient apiClient;

View file

@ -6,23 +6,21 @@ import 'package:fooder/models/entry.dart';
import 'package:fooder/widgets/product.dart'; import 'package:fooder/widgets/product.dart';
import 'package:fooder/screens/add_product.dart'; import 'package:fooder/screens/add_product.dart';
class EditEntryScreen extends BasedScreen { class EditEntryScreen extends BasedScreen {
final Entry entry; final Entry entry;
const EditEntryScreen({super.key, required super.apiClient, required this.entry}); const EditEntryScreen(
{super.key, required super.apiClient, required this.entry});
@override @override
State<EditEntryScreen> createState() => _EditEntryScreen(); State<EditEntryScreen> createState() => _EditEntryScreen();
} }
class _EditEntryScreen extends State<EditEntryScreen> { class _EditEntryScreen extends State<EditEntryScreen> {
final gramsController = TextEditingController(); final gramsController = TextEditingController();
final productNameController = TextEditingController(); final productNameController = TextEditingController();
List<Product> products = []; List<Product> products = [];
@override @override
void dispose() { void dispose() {
gramsController.dispose(); gramsController.dispose();
@ -35,7 +33,7 @@ class _EditEntryScreen extends State<EditEntryScreen> {
} }
@override @override
void initState () { void initState() {
super.initState(); super.initState();
setState(() { setState(() {
gramsController.text = widget.entry.grams.toString(); gramsController.text = widget.entry.grams.toString();
@ -45,14 +43,16 @@ class _EditEntryScreen extends State<EditEntryScreen> {
} }
Future<void> _getProducts() async { Future<void> _getProducts() async {
var productsMap = await widget.apiClient.getProducts(productNameController.text); var productsMap =
await widget.apiClient.getProducts(productNameController.text);
setState(() { setState(() {
products = (productsMap['products'] as List<dynamic>).map((e) => Product.fromJson(e as Map<String, dynamic>)).toList(); products = (productsMap['products'] as List<dynamic>)
.map((e) => Product.fromJson(e as Map<String, dynamic>))
.toList();
}); });
} }
void showError(String message) void showError(String message) {
{
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(message, textAlign: TextAlign.center), content: Text(message, textAlign: TextAlign.center),
@ -104,17 +104,18 @@ class _EditEntryScreen extends State<EditEntryScreen> {
), ),
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: ListView( child: ListView(children: <Widget>[
children: <Widget>[
TextFormField( TextFormField(
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Grams', labelText: 'Grams',
), ),
keyboardType:const TextInputType.numberWithOptions(decimal: true), keyboardType:
const TextInputType.numberWithOptions(decimal: true),
inputFormatters: <TextInputFormatter>[ inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.allow(RegExp(r'^(\d+)?[\.,]?\d{0,2}')), FilteringTextInputFormatter.allow(
RegExp(r'^(\d+)?[\.,]?\d{0,2}')),
], ],
controller: gramsController, controller: gramsController,
), ),
@ -148,19 +149,17 @@ class _EditEntryScreen extends State<EditEntryScreen> {
), ),
for (var product in products) for (var product in products)
ListTile( ListTile(
onTap: () { onTap: () {
setState(() { setState(() {
products = [product]; products = [product];
productNameController.text = product.name; productNameController.text = product.name;
}); });
}, },
title: ProductWidget( title: ProductWidget(
product: product, product: product,
),
), ),
), ])),
]
)
),
), ),
floatingActionButton: Row( floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,

View file

@ -4,7 +4,6 @@ import 'package:fooder/screens/based.dart';
import 'package:fooder/screens/main.dart'; import 'package:fooder/screens/main.dart';
import 'package:fooder/screens/register.dart'; import 'package:fooder/screens/register.dart';
class LoginScreen extends BasedScreen { class LoginScreen extends BasedScreen {
const LoginScreen({super.key, required super.apiClient}); const LoginScreen({super.key, required super.apiClient});
@ -12,7 +11,6 @@ class LoginScreen extends BasedScreen {
State<LoginScreen> createState() => _LoginScreen(); State<LoginScreen> createState() => _LoginScreen();
} }
class _LoginScreen extends State<LoginScreen> { class _LoginScreen extends State<LoginScreen> {
final usernameController = TextEditingController(); final usernameController = TextEditingController();
final passwordController = TextEditingController(); final passwordController = TextEditingController();
@ -24,8 +22,7 @@ class _LoginScreen extends State<LoginScreen> {
super.dispose(); super.dispose();
} }
void showError(String message) void showError(String message) {
{
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(message, textAlign: TextAlign.center), content: Text(message, textAlign: TextAlign.center),
@ -34,8 +31,7 @@ class _LoginScreen extends State<LoginScreen> {
); );
} }
void showText(String text) void showText(String text) {
{
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(text, textAlign: TextAlign.center), content: Text(text, textAlign: TextAlign.center),
@ -68,9 +64,10 @@ class _LoginScreen extends State<LoginScreen> {
} }
@override @override
void initState () { void initState() {
super.initState(); super.initState();
SystemChannels.textInput.invokeMethod('TextInput.setClientFeatures', <String, dynamic>{ SystemChannels.textInput
.invokeMethod('TextInput.setClientFeatures', <String, dynamic>{
'setAuthenticationConfiguration': true, 'setAuthenticationConfiguration': true,
'setAutofillHints': <String>[ 'setAutofillHints': <String>[
AutofillHints.username, AutofillHints.username,
@ -140,7 +137,8 @@ class _LoginScreen extends State<LoginScreen> {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => RegisterScreen(apiClient: widget.apiClient), builder: (context) =>
RegisterScreen(apiClient: widget.apiClient),
), ),
); );
}, },

View file

@ -5,7 +5,6 @@ import 'package:fooder/screens/add_entry.dart';
import 'package:fooder/models/diary.dart'; import 'package:fooder/models/diary.dart';
import 'package:fooder/widgets/diary.dart'; import 'package:fooder/widgets/diary.dart';
class MainScreen extends BasedScreen { class MainScreen extends BasedScreen {
const MainScreen({super.key, required super.apiClient}); const MainScreen({super.key, required super.apiClient});
@ -18,7 +17,7 @@ class _MainScreen extends State<MainScreen> {
DateTime date = DateTime.now(); DateTime date = DateTime.now();
@override @override
void initState () { void initState() {
super.initState(); super.initState();
_asyncInitState().then((value) => null); _asyncInitState().then((value) => null);
} }
@ -55,7 +54,8 @@ class _MainScreen extends State<MainScreen> {
await Navigator.push( await Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => AddEntryScreen(apiClient: widget.apiClient, diary: diary!), builder: (context) =>
AddEntryScreen(apiClient: widget.apiClient, diary: diary!),
), ),
).then((_) => _asyncInitState()); ).then((_) => _asyncInitState());
} }
@ -69,17 +69,23 @@ class _MainScreen extends State<MainScreen> {
content = Container( content = Container(
constraints: const BoxConstraints(maxWidth: 720), constraints: const BoxConstraints(maxWidth: 720),
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
child: DiaryWidget(diary: diary!, apiClient: widget.apiClient, refreshParent: _asyncInitState), child: DiaryWidget(
diary: diary!,
apiClient: widget.apiClient,
refreshParent: _asyncInitState),
); );
title = Row( title = Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
TextButton( TextButton(
child: const Text("🅵🅾🅾🅳🅴🆁", style: const TextStyle(fontSize: 20, color: Colors.white)), child: const Text("🅵🅾🅾🅳🅴🆁",
style: const TextStyle(fontSize: 20, color: Colors.white)),
onPressed: () { onPressed: () {
Navigator.pushReplacement( Navigator.pushReplacement(
context, context,
MaterialPageRoute(builder: (context) => MainScreen(apiClient: widget.apiClient)), MaterialPageRoute(
builder: (context) =>
MainScreen(apiClient: widget.apiClient)),
).then((_) => _asyncInitState()); ).then((_) => _asyncInitState());
}, },
), ),

View file

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:fooder/screens/based.dart'; import 'package:fooder/screens/based.dart';
class RegisterScreen extends BasedScreen { class RegisterScreen extends BasedScreen {
const RegisterScreen({super.key, required super.apiClient}); const RegisterScreen({super.key, required super.apiClient});
@ -10,7 +9,6 @@ class RegisterScreen extends BasedScreen {
State<RegisterScreen> createState() => _RegisterScreen(); State<RegisterScreen> createState() => _RegisterScreen();
} }
class _RegisterScreen extends State<RegisterScreen> { class _RegisterScreen extends State<RegisterScreen> {
final usernameController = TextEditingController(); final usernameController = TextEditingController();
final passwordController = TextEditingController(); final passwordController = TextEditingController();
@ -27,7 +25,8 @@ class _RegisterScreen extends State<RegisterScreen> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
SystemChannels.textInput.invokeMethod('TextInput.setClientFeatures', <String, dynamic>{ SystemChannels.textInput
.invokeMethod('TextInput.setClientFeatures', <String, dynamic>{
'setAuthenticationConfiguration': true, 'setAuthenticationConfiguration': true,
'setAutofillHints': <String>[ 'setAutofillHints': <String>[
AutofillHints.username, AutofillHints.username,
@ -36,8 +35,7 @@ class _RegisterScreen extends State<RegisterScreen> {
}); });
} }
void showError(String message) void showError(String message) {
{
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(message, textAlign: TextAlign.center), content: Text(message, textAlign: TextAlign.center),
@ -46,8 +44,7 @@ class _RegisterScreen extends State<RegisterScreen> {
); );
} }
void showText(String text) void showText(String text) {
{
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(text, textAlign: TextAlign.center), content: Text(text, textAlign: TextAlign.center),
@ -110,21 +107,19 @@ class _RegisterScreen extends State<RegisterScreen> {
autofillHints: const [AutofillHints.password], autofillHints: const [AutofillHints.password],
), ),
TextFormField( TextFormField(
obscureText: true, obscureText: true,
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Confirm password', labelText: 'Confirm password',
), ),
controller: passwordConfirmController, controller: passwordConfirmController,
autofillHints: const [AutofillHints.password], autofillHints: const [AutofillHints.password],
onFieldSubmitted: (_) => _register() onFieldSubmitted: (_) => _register()),
),
Padding( Padding(
padding: const EdgeInsets.symmetric(vertical: 10), padding: const EdgeInsets.symmetric(vertical: 10),
child: FilledButton( child: FilledButton(
onPressed: _register, onPressed: _register,
child: const Text('Register'), child: const Text('Register'),
) )),
),
], ],
), ),
), ),

View file

@ -6,13 +6,16 @@ import 'package:fooder/client.dart';
import 'package:fooder/screens/add_meal.dart'; import 'package:fooder/screens/add_meal.dart';
import 'dart:core'; import 'dart:core';
class DiaryWidget extends StatelessWidget { class DiaryWidget extends StatelessWidget {
final Diary diary; final Diary diary;
final ApiClient apiClient; final ApiClient apiClient;
final Function() refreshParent; final Function() refreshParent;
const DiaryWidget({super.key, required this.diary, required this.apiClient, required this.refreshParent}); const DiaryWidget(
{super.key,
required this.diary,
required this.apiClient,
required this.refreshParent});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -21,34 +24,36 @@ class DiaryWidget extends StatelessWidget {
child: CustomScrollView( child: CustomScrollView(
slivers: <Widget>[ slivers: <Widget>[
SliverAppBar( SliverAppBar(
title: Row( title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[ children: <Widget>[
const Spacer(), const Spacer(),
Text( Text(
"${diary.calories.toStringAsFixed(1)} kcal", "${diary.calories.toStringAsFixed(1)} kcal",
style: Theme.of(context).textTheme.headlineLarge!.copyWith( style: Theme.of(context)
color: Theme.of(context).colorScheme.onSecondary, .textTheme
fontWeight: FontWeight.bold, .headlineLarge!
), .copyWith(
), color: Theme.of(context).colorScheme.onSecondary,
] fontWeight: FontWeight.bold,
), ),
expandedHeight: 150, ),
backgroundColor: Theme.of(context).colorScheme.secondary, ]),
shape: RoundedRectangleBorder( expandedHeight: 150,
borderRadius: BorderRadius.circular(8), backgroundColor: Theme.of(context).colorScheme.secondary,
), shape: RoundedRectangleBorder(
floating: true, borderRadius: BorderRadius.circular(8),
flexibleSpace: FlexibleSpaceBar( ),
title: MacroWidget( floating: true,
flexibleSpace: FlexibleSpaceBar(
title: MacroWidget(
protein: diary.protein, protein: diary.protein,
carb: diary.carb, carb: diary.carb,
fat: diary.fat, fat: diary.fat,
style: Theme.of(context).textTheme.bodyMedium!.copyWith( style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onSecondary, color: Theme.of(context).colorScheme.onSecondary,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
child: IconButton( child: IconButton(
onPressed: () { onPressed: () {
Navigator.push( Navigator.push(
@ -63,15 +68,16 @@ class DiaryWidget extends StatelessWidget {
refreshParent(); refreshParent();
}); });
}, },
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
style: ButtonStyle( style: ButtonStyle(
backgroundColor: MaterialStateProperty.all<Color>(Theme.of(context).colorScheme.secondary), backgroundColor: MaterialStateProperty.all<Color>(
foregroundColor: MaterialStateProperty.all<Color>(Theme.of(context).colorScheme.onSecondary), Theme.of(context).colorScheme.secondary),
), foregroundColor: MaterialStateProperty.all<Color>(
Theme.of(context).colorScheme.onSecondary),
),
), ),
), ),
) )),
),
SliverList( SliverList(
delegate: SliverChildListDelegate( delegate: SliverChildListDelegate(
[ [
@ -80,7 +86,7 @@ class DiaryWidget extends StatelessWidget {
meal: meal, meal: meal,
apiClient: apiClient, apiClient: apiClient,
refreshParent: refreshParent, refreshParent: refreshParent,
), ),
], ],
), ),
), ),

View file

@ -3,7 +3,6 @@ import 'package:fooder/models/entry.dart';
import 'package:fooder/widgets/macro.dart'; import 'package:fooder/widgets/macro.dart';
import 'dart:core'; import 'dart:core';
class EntryWidget extends StatelessWidget { class EntryWidget extends StatelessWidget {
final Entry entry; final Entry entry;
@ -32,8 +31,8 @@ class EntryWidget extends StatelessWidget {
fat: entry.fat, fat: entry.fat,
amount: entry.grams, amount: entry.grams,
style: Theme.of(context).textTheme.bodyMedium!.copyWith( style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.secondary, color: Theme.of(context).colorScheme.secondary,
), ),
), ),
], ],
), ),

View file

@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class MacroWidget extends StatelessWidget { class MacroWidget extends StatelessWidget {
final double? amount; final double? amount;
final double? calories; final double? calories;
@ -26,28 +25,28 @@ class MacroWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var elements = <Widget>[ var elements = <Widget>[
Expanded( Expanded(
flex: 1, flex: 1,
child: Text( child: Text(
"C: ${carb.toStringAsFixed(1)}g", "C: ${carb.toStringAsFixed(1)}g",
style: style, style: style,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),
Expanded( Expanded(
flex: 1, flex: 1,
child: Text( child: Text(
"F: ${fat.toStringAsFixed(1)}g", "F: ${fat.toStringAsFixed(1)}g",
style: style, style: style,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),
Expanded( Expanded(
flex: 1, flex: 1,
child: Text( child: Text(
"P: ${protein.toStringAsFixed(1)}g", "P: ${protein.toStringAsFixed(1)}g",
style: style, style: style,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),
]; ];
@ -86,7 +85,7 @@ class MacroWidget extends StatelessWidget {
"${amount!.toStringAsFixed(1)}g", "${amount!.toStringAsFixed(1)}g",
style: style, style: style,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),
); );
} }

View file

@ -6,52 +6,55 @@ import 'package:fooder/screens/edit_entry.dart';
import 'package:fooder/client.dart'; import 'package:fooder/client.dart';
import 'dart:core'; import 'dart:core';
class MealWidget extends StatelessWidget { class MealWidget extends StatelessWidget {
final Meal meal; final Meal meal;
final ApiClient apiClient; final ApiClient apiClient;
final Function() refreshParent; final Function() refreshParent;
const MealWidget({super.key, required this.meal, required this.apiClient, required this.refreshParent}); const MealWidget(
{super.key,
required this.meal,
required this.apiClient,
required this.refreshParent});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Card( return Card(
child: Padding( child: Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
top: 36.0, left: 6.0, right: 6.0, bottom: 6.0), top: 36.0, left: 6.0, right: 6.0, bottom: 6.0),
child: ExpansionTile( child: ExpansionTile(
title: Column( title: Column(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: Text(
meal.name,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: Theme.of(context).colorScheme.primary,
),
),
),
Text("${meal.calories.toStringAsFixed(1)} kcal"),
],
),
MacroWidget(
protein: meal.protein,
carb: meal.carb,
fat: meal.fat,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.secondary,
),
),
],
),
children: <Widget>[ children: <Widget>[
for (var entry in meal.entries) Row(
ListTile( children: <Widget>[
title: EntryWidget( Expanded(
entry: entry, child: Text(
meal.name,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: Theme.of(context).colorScheme.primary,
),
),
), ),
Text("${meal.calories.toStringAsFixed(1)} kcal"),
],
),
MacroWidget(
protein: meal.protein,
carb: meal.carb,
fat: meal.fat,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.secondary,
),
),
],
),
children: <Widget>[
for (var entry in meal.entries)
ListTile(
title: EntryWidget(
entry: entry,
),
onTap: () { onTap: () {
Navigator.push( Navigator.push(
context, context,
@ -65,7 +68,7 @@ class MealWidget extends StatelessWidget {
refreshParent(); refreshParent();
}); });
}, },
) )
], ],
), ),
), ),

View file

@ -3,7 +3,6 @@ import 'package:fooder/models/product.dart';
import 'package:fooder/widgets/macro.dart'; import 'package:fooder/widgets/macro.dart';
import 'dart:core'; import 'dart:core';
class ProductWidget extends StatelessWidget { class ProductWidget extends StatelessWidget {
final Product product; final Product product;
@ -32,9 +31,9 @@ class ProductWidget extends StatelessWidget {
fat: product.fat, fat: product.fat,
fiber: product.fiber, fiber: product.fiber,
style: Theme.of(context).textTheme.bodyMedium!.copyWith( style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.secondary, color: Theme.of(context).colorScheme.secondary,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
], ],
), ),