initial state done
This commit is contained in:
parent
669dff82d4
commit
43dd4a8543
12 changed files with 449 additions and 34 deletions
|
@ -40,6 +40,14 @@ class ApiClient {
|
||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> _jsonDecode(http.Response response) {
|
||||||
|
try {
|
||||||
|
return jsonDecode(utf8.decode(response.bodyBytes));
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Response returned status code: ${response.statusCode}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> get(String path) async {
|
Future<Map<String, dynamic>> get(String path) async {
|
||||||
final response = await httpClient.get(
|
final response = await httpClient.get(
|
||||||
Uri.parse('$baseUrl$path'),
|
Uri.parse('$baseUrl$path'),
|
||||||
|
@ -50,7 +58,7 @@ class ApiClient {
|
||||||
throw Exception('Response returned status code: ${response.statusCode}');
|
throw Exception('Response returned status code: ${response.statusCode}');
|
||||||
}
|
}
|
||||||
|
|
||||||
return jsonDecode(response.body);
|
return _jsonDecode(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> post(String path, Map<String, dynamic> body) async {
|
Future<Map<String, dynamic>> post(String path, Map<String, dynamic> body) async {
|
||||||
|
@ -64,7 +72,7 @@ class ApiClient {
|
||||||
throw Exception('Response returned status code: ${response.statusCode}');
|
throw Exception('Response returned status code: ${response.statusCode}');
|
||||||
}
|
}
|
||||||
|
|
||||||
return jsonDecode(response.body);
|
return _jsonDecode(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -78,7 +86,7 @@ class ApiClient {
|
||||||
throw Exception('Response returned status code: ${response.statusCode}');
|
throw Exception('Response returned status code: ${response.statusCode}');
|
||||||
}
|
}
|
||||||
|
|
||||||
return jsonDecode(response.body);
|
return _jsonDecode(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> patch(String path, Map<String, dynamic> body) async {
|
Future<Map<String, dynamic>> patch(String path, Map<String, dynamic> body) async {
|
||||||
|
@ -92,7 +100,7 @@ class ApiClient {
|
||||||
throw Exception('Response returned status code: ${response.statusCode}');
|
throw Exception('Response returned status code: ${response.statusCode}');
|
||||||
}
|
}
|
||||||
|
|
||||||
return jsonDecode(response.body);
|
return _jsonDecode(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> login(String username, String password) async {
|
Future<void> login(String username, String password) async {
|
||||||
|
@ -114,11 +122,11 @@ class ApiClient {
|
||||||
throw Exception('Failed to login');
|
throw Exception('Failed to login');
|
||||||
}
|
}
|
||||||
|
|
||||||
final token = jsonDecode(response.body)['access_token'];
|
final token = _jsonDecode(response)['access_token'];
|
||||||
this.token = token;
|
this.token = token;
|
||||||
window.localStorage['token'] = token;
|
window.localStorage['token'] = token;
|
||||||
|
|
||||||
final refreshToken = jsonDecode(response.body)['refresh_token'];
|
final refreshToken = _jsonDecode(response)['refresh_token'];
|
||||||
this.refreshToken = refreshToken;
|
this.refreshToken = refreshToken;
|
||||||
window.localStorage['refreshToken'] = refreshToken;
|
window.localStorage['refreshToken'] = refreshToken;
|
||||||
}
|
}
|
||||||
|
@ -141,7 +149,37 @@ class ApiClient {
|
||||||
window.localStorage['refreshToken'] = refreshToken!;
|
window.localStorage['refreshToken'] = refreshToken!;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> getDiary() async {
|
Future<Map<String, dynamic>> getDiary({required DateTime date}) async {
|
||||||
return await get("/diary?date=2023-07-29");
|
var params = {
|
||||||
|
"date": "${date.year}-${date.month}-${date.day}",
|
||||||
|
};
|
||||||
|
var response = await get("/diary?${Uri(queryParameters: params).query}");
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
void logout() {
|
||||||
|
token = null;
|
||||||
|
refreshToken = null;
|
||||||
|
window.localStorage.remove('token');
|
||||||
|
window.localStorage.remove('refreshToken');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> getProducts(String q) async {
|
||||||
|
var response = await get("/product?${Uri(queryParameters: {"q": q}).query}");
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addEntry({
|
||||||
|
required double grams,
|
||||||
|
required int productId,
|
||||||
|
required int mealId,
|
||||||
|
}
|
||||||
|
) async {
|
||||||
|
var entry = {
|
||||||
|
"grams": grams,
|
||||||
|
"product_id": productId,
|
||||||
|
"meal_id": mealId,
|
||||||
|
};
|
||||||
|
await post("/entry", entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ class Diary {
|
||||||
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 = <Meal>[],
|
meals = (map['meals'] as List<dynamic>).map((e) => Meal.fromJson(e as Map<String, dynamic>)).toList(),
|
||||||
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,
|
||||||
|
|
|
@ -21,4 +21,14 @@ class Entry {
|
||||||
required this.fat,
|
required this.fat,
|
||||||
required this.carb,
|
required this.carb,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Entry.fromJson(Map<String, dynamic> map):
|
||||||
|
id = map['id'] as int,
|
||||||
|
grams = map['grams'] as double,
|
||||||
|
product = Product.fromJson(map['product'] as Map<String, dynamic>),
|
||||||
|
mealId = map['meal_id'] as int,
|
||||||
|
calories = map['calories'] as double,
|
||||||
|
protein = map['protein'] as double,
|
||||||
|
fat = map['fat'] as double,
|
||||||
|
carb = map['carb'] as double;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,35 @@ import 'package:fooder_web/models/entry.dart';
|
||||||
|
|
||||||
class Meal {
|
class Meal {
|
||||||
final List<Entry> entries;
|
final List<Entry> entries;
|
||||||
|
final int id;
|
||||||
|
final String name;
|
||||||
|
final int order;
|
||||||
|
final double calories;
|
||||||
|
final double protein;
|
||||||
|
final double carb;
|
||||||
|
final double fat;
|
||||||
|
final int diaryId;
|
||||||
|
|
||||||
const Meal({
|
Meal({
|
||||||
required this.entries,
|
required this.entries,
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.order,
|
||||||
|
required this.calories,
|
||||||
|
required this.protein,
|
||||||
|
required this.carb,
|
||||||
|
required this.fat,
|
||||||
|
required this.diaryId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Meal.fromJson(Map<String, dynamic> map):
|
||||||
|
entries = (map['entries'] as List<dynamic>).map((e) => Entry.fromJson(e as Map<String, dynamic>)).toList(),
|
||||||
|
id = map['id'] as int,
|
||||||
|
name = map['name'] as String,
|
||||||
|
order = map['order'] as int,
|
||||||
|
calories = map['calories'] as double,
|
||||||
|
protein = map['protein'] as double,
|
||||||
|
carb = map['carb'] as double,
|
||||||
|
fat = map['fat'] as double,
|
||||||
|
diaryId = map['diary_id'] as int;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,4 +14,12 @@ class Product {
|
||||||
required this.carb,
|
required this.carb,
|
||||||
required this.fat,
|
required this.fat,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Product.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;
|
||||||
}
|
}
|
||||||
|
|
128
lib/screens/add_entry.dart
Normal file
128
lib/screens/add_entry.dart
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:fooder_web/screens/based.dart';
|
||||||
|
import 'package:fooder_web/models/product.dart';
|
||||||
|
import 'package:fooder_web/models/diary.dart';
|
||||||
|
import 'package:fooder_web/widgets/product.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class AddEntryScreen extends BasedScreen {
|
||||||
|
final Diary diary;
|
||||||
|
|
||||||
|
const AddEntryScreen({super.key, required super.apiClient, required this.diary});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AddEntryScreen> createState() => _AddEntryScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class _AddEntryScreen extends State<AddEntryScreen> {
|
||||||
|
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();
|
||||||
|
_getProducts().then((value) => null);
|
||||||
|
}
|
||||||
|
|
||||||
|
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> _addEntry() async {
|
||||||
|
if (products.length != 1) {
|
||||||
|
showError("Pick product first");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
double.parse(gramsController.text);
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
showError("Grams must be a number");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await widget.apiClient.addEntry(
|
||||||
|
grams: double.parse(gramsController.text),
|
||||||
|
productId: products[0].id,
|
||||||
|
mealId: widget.diary.meals[0].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: FloatingActionButton(
|
||||||
|
onPressed: _addEntry,
|
||||||
|
child: const Icon(Icons.add),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -72,6 +72,10 @@ class _LoginScreen extends State<LoginScreen> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _asyncInitState() async {
|
Future<void> _asyncInitState() async {
|
||||||
|
if (widget.apiClient.refreshToken == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await widget.apiClient.refresh();
|
await widget.apiClient.refresh();
|
||||||
showText("Welcome back!");
|
showText("Welcome back!");
|
||||||
|
@ -107,6 +111,7 @@ class _LoginScreen extends State<LoginScreen> {
|
||||||
labelText: 'Password',
|
labelText: 'Password',
|
||||||
),
|
),
|
||||||
controller: passwordController,
|
controller: passwordController,
|
||||||
|
onFieldSubmitted: (_) => _login()
|
||||||
),
|
),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: _login,
|
onPressed: _login,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:fooder_web/screens/based.dart';
|
import 'package:fooder_web/screens/based.dart';
|
||||||
import 'package:fooder_web/models/meal.dart';
|
import 'package:fooder_web/screens/login.dart';
|
||||||
import 'package:fooder_web/models/entry.dart';
|
import 'package:fooder_web/screens/add_entry.dart';
|
||||||
import 'package:fooder_web/models/diary.dart';
|
import 'package:fooder_web/models/diary.dart';
|
||||||
import 'package:fooder_web/widgets/diary.dart';
|
import 'package:fooder_web/widgets/diary.dart';
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ class MainScreen extends BasedScreen {
|
||||||
|
|
||||||
class _MainScreen extends State<MainScreen> {
|
class _MainScreen extends State<MainScreen> {
|
||||||
Diary? diary;
|
Diary? diary;
|
||||||
|
DateTime date = DateTime.now();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState () {
|
void initState () {
|
||||||
|
@ -23,36 +24,90 @@ class _MainScreen extends State<MainScreen> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _asyncInitState() async {
|
Future<void> _asyncInitState() async {
|
||||||
var diaryMap = await widget.apiClient.getDiary();
|
var diaryMap = await widget.apiClient.getDiary(date: date);
|
||||||
setState(() {
|
setState(() {
|
||||||
diary = Diary.fromJson(diaryMap);
|
diary = Diary.fromJson(diaryMap);
|
||||||
|
date = date;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _pickDate() async {
|
||||||
|
date = (await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: date,
|
||||||
|
firstDate: DateTime(2020),
|
||||||
|
lastDate: DateTime(2025),
|
||||||
|
))!;
|
||||||
|
await _asyncInitState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _logout() async {
|
||||||
|
widget.apiClient.logout();
|
||||||
|
Navigator.pushReplacement(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => LoginScreen(apiClient: widget.apiClient),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _addEntry() async {
|
||||||
|
await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => AddEntryScreen(apiClient: widget.apiClient, diary: diary!),
|
||||||
|
),
|
||||||
|
).then((_) => _asyncInitState());
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var content;
|
Widget content;
|
||||||
var title = "FOODER";
|
Widget title;
|
||||||
|
|
||||||
if (diary != null) {
|
if (diary != null) {
|
||||||
content = Container(
|
content = Container(
|
||||||
constraints: const BoxConstraints(maxWidth: 600),
|
constraints: const BoxConstraints(maxWidth: 720),
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
child: DiaryWidget(diary: diary!),
|
child: DiaryWidget(diary: diary!),
|
||||||
);
|
);
|
||||||
title = "FOODER - ${diary!.date.year}-${diary!.date.month}-${diary!.date.day}";
|
title = Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
const Text("🅵🅾🅾🅳🅴🆁"),
|
||||||
|
const Spacer(),
|
||||||
|
Text(
|
||||||
|
"${date.year}-${date.month}-${date.day}",
|
||||||
|
style: const TextStyle(fontSize: 20),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.calendar_month),
|
||||||
|
onPressed: _pickDate,
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.logout),
|
||||||
|
onPressed: _logout,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
content = const CircularProgressIndicator();
|
content = const CircularProgressIndicator();
|
||||||
|
title = const Text("🅵🅾🅾🅳🅴🆁");
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
||||||
title: Text(title),
|
title: title,
|
||||||
),
|
),
|
||||||
body: Center(
|
body: Center(
|
||||||
child: content,
|
child: content,
|
||||||
),
|
),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
onPressed: _addEntry,
|
||||||
|
child: const Icon(Icons.add),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,36 @@ class DiaryWidget extends StatelessWidget {
|
||||||
MealWidget(
|
MealWidget(
|
||||||
meal: meal,
|
meal: meal,
|
||||||
),
|
),
|
||||||
Text(diary.date.toString()),
|
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),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -12,7 +12,42 @@ class EntryWidget extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
child: Text(entry.product.name),
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
entry.product.name,
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text("${entry.calories.toStringAsFixed(2)} kcal"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
"carb: ${entry.carb.toStringAsFixed(2)}",
|
||||||
|
style: TextStyle(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),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,38 @@ class MealWidget extends StatelessWidget {
|
||||||
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: const Text('SEKS Z KOBIETA'),
|
title: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
meal.name,
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text("${meal.calories.toStringAsFixed(2)} kcal"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
"carb: ${meal.carb.toStringAsFixed(2)}",
|
||||||
|
style: TextStyle(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>[
|
children: <Widget>[
|
||||||
for (var entry in meal.entries)
|
for (var entry in meal.entries)
|
||||||
EntryWidget(
|
EntryWidget(
|
||||||
|
|
49
lib/widgets/product.dart
Normal file
49
lib/widgets/product.dart
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:fooder_web/models/product.dart';
|
||||||
|
import 'dart:core';
|
||||||
|
|
||||||
|
|
||||||
|
class ProductWidget extends StatelessWidget {
|
||||||
|
final Product product;
|
||||||
|
|
||||||
|
const ProductWidget({super.key, required this.product});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
product.name,
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text("${product.calories.toStringAsFixed(2)} kcal"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
"carb: ${product.carb.toStringAsFixed(2)}",
|
||||||
|
style: TextStyle(color: Theme.of(context).colorScheme.secondary),
|
||||||
|
),
|
||||||
|
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