[context] refactor screens etc to use global context storing client and storage, for easier further changes'
This commit is contained in:
parent
07c92443ce
commit
33fe0eff39
20 changed files with 521 additions and 488 deletions
351
lib/client.dart
351
lib/client.dart
|
@ -1,328 +1,35 @@
|
|||
import 'package:http/http.dart' as http;
|
||||
import 'dart:convert';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:fooder/models/meal.dart';
|
||||
import 'package:fooder/client/api.dart';
|
||||
import 'package:fooder/client/product.dart';
|
||||
import 'package:fooder/client/entry.dart';
|
||||
import 'package:fooder/client/preset.dart';
|
||||
import 'package:fooder/client/meal.dart';
|
||||
import 'package:fooder/client/diary.dart';
|
||||
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
class Client {
|
||||
final ApiClient api;
|
||||
final ProductClient product;
|
||||
final EntryClient entry;
|
||||
final PresetClient preset;
|
||||
final MealClient meal;
|
||||
final DiaryClient diary;
|
||||
|
||||
class ApiClient {
|
||||
final String baseUrl;
|
||||
String? token;
|
||||
String? refreshToken;
|
||||
http.Client httpClient = http.Client();
|
||||
final FlutterSecureStorage storage = const FlutterSecureStorage();
|
||||
Client(
|
||||
{required this.api,
|
||||
required this.product,
|
||||
required this.entry,
|
||||
required this.meal,
|
||||
required this.diary,
|
||||
required this.preset});
|
||||
|
||||
ApiClient({
|
||||
required this.baseUrl,
|
||||
});
|
||||
static Future<Client> create({required String baseUrl}) async {
|
||||
var api = await ApiClient.create(baseUrl: baseUrl);
|
||||
|
||||
static Future<ApiClient> create({required String baseUrl}) async {
|
||||
var client = ApiClient(baseUrl: baseUrl);
|
||||
client.loadToken();
|
||||
return client;
|
||||
}
|
||||
|
||||
Future<void> loadToken() async {
|
||||
Map<String, String> allValues = await storage.readAll();
|
||||
|
||||
if (allValues.containsKey('token')) {
|
||||
token = allValues['token'];
|
||||
}
|
||||
if (allValues.containsKey('refreshToken')) {
|
||||
refreshToken = allValues['refreshToken'];
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, String> headers({bool forGet = false, bool forLogin = false}) {
|
||||
if (token == null && !forLogin) {
|
||||
throw Exception('Not logged in');
|
||||
}
|
||||
|
||||
final headers = {
|
||||
'Accept': 'application/json',
|
||||
};
|
||||
|
||||
if (!forGet) {
|
||||
headers['Content-Type'] = 'application/json';
|
||||
}
|
||||
|
||||
if (token != null) {
|
||||
headers['Authorization'] = 'Bearer $token';
|
||||
}
|
||||
|
||||
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 {
|
||||
final response = await httpClient.get(
|
||||
Uri.parse('$baseUrl$path'),
|
||||
headers: headers(forGet: true),
|
||||
);
|
||||
|
||||
if (response.statusCode == 401) {
|
||||
await refresh();
|
||||
return await get(path);
|
||||
}
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Response returned status code: ${response.statusCode}');
|
||||
}
|
||||
|
||||
return _jsonDecode(response);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> post(String path, Map<String, dynamic> body,
|
||||
{bool forLogin = 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 post(path, body, forLogin: forLogin);
|
||||
}
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Response returned status code: ${response.statusCode}');
|
||||
}
|
||||
|
||||
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 {
|
||||
final response = await httpClient.delete(
|
||||
Uri.parse('$baseUrl$path'),
|
||||
headers: headers(),
|
||||
);
|
||||
|
||||
if (response.statusCode == 401) {
|
||||
await refresh();
|
||||
return await delete(path);
|
||||
}
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Response returned status code: ${response.statusCode}');
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> patch(
|
||||
String path, Map<String, dynamic> body) async {
|
||||
final response = await httpClient.patch(
|
||||
Uri.parse('$baseUrl$path'),
|
||||
body: jsonEncode(body),
|
||||
headers: headers(),
|
||||
);
|
||||
|
||||
if (response.statusCode == 401) {
|
||||
await refresh();
|
||||
return await patch(path, body);
|
||||
}
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Response returned status code: ${response.statusCode}');
|
||||
}
|
||||
|
||||
return _jsonDecode(response);
|
||||
}
|
||||
|
||||
Future<void> login(String username, String password) async {
|
||||
final headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Accept': 'application/json',
|
||||
};
|
||||
|
||||
final response = await httpClient.post(
|
||||
Uri.parse('$baseUrl/token'),
|
||||
body: {
|
||||
'username': username,
|
||||
'password': password,
|
||||
},
|
||||
encoding: Encoding.getByName('utf-8'),
|
||||
headers: headers,
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Failed to login');
|
||||
}
|
||||
|
||||
final token = _jsonDecode(response)['access_token'];
|
||||
this.token = token;
|
||||
await storage.write(key: 'token', value: token);
|
||||
|
||||
final refreshToken = _jsonDecode(response)['refresh_token'];
|
||||
this.refreshToken = refreshToken;
|
||||
await storage.write(key: 'refreshToken', value: refreshToken);
|
||||
}
|
||||
|
||||
Future<void> refresh() async {
|
||||
if (refreshToken == null) {
|
||||
throw Exception("No valid refresh token found");
|
||||
}
|
||||
|
||||
final response = await post("/token/refresh", {
|
||||
"refresh_token": refreshToken,
|
||||
});
|
||||
|
||||
token = response['access_token'] as String;
|
||||
await storage.write(key: 'token', value: token);
|
||||
|
||||
refreshToken = response['refresh_token'] as String;
|
||||
await storage.write(key: 'refreshToken', value: refreshToken);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getDiary({required DateTime date}) async {
|
||||
var formatter = DateFormat('yyyy-MM-dd');
|
||||
var params = {
|
||||
"date": formatter.format(date),
|
||||
};
|
||||
var response = await get("/diary?${Uri(queryParameters: params).query}");
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<void> logout() async {
|
||||
token = null;
|
||||
refreshToken = null;
|
||||
|
||||
await storage.deleteAll();
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getProducts(String q) async {
|
||||
var response =
|
||||
await get("/product?${Uri(queryParameters: {"q": q}).query}");
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getProductByBarcode(String barcode) async {
|
||||
var response = await get("/product/by_barcode?${Uri(queryParameters: {
|
||||
"barcode": barcode
|
||||
}).query}");
|
||||
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({
|
||||
required double grams,
|
||||
required int productId,
|
||||
required int mealId,
|
||||
}) async {
|
||||
var entry = {
|
||||
"grams": grams,
|
||||
"product_id": productId,
|
||||
"meal_id": mealId,
|
||||
};
|
||||
await post("/entry", entry);
|
||||
}
|
||||
|
||||
Future<void> deleteEntry(int id) async {
|
||||
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(
|
||||
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);
|
||||
}
|
||||
|
||||
Future<void> register(String username, String password) async {
|
||||
try {
|
||||
await post(
|
||||
"/user",
|
||||
{
|
||||
"username": username,
|
||||
"password": password,
|
||||
},
|
||||
forLogin: true,
|
||||
);
|
||||
} catch (e) {
|
||||
throw Exception("Failed to register");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> addMeal({required String name, required int diaryId}) async {
|
||||
await post("/meal", {
|
||||
"name": name,
|
||||
"diary_id": diaryId,
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> addMealFromPreset(
|
||||
{required String name,
|
||||
required int diaryId,
|
||||
required int presetId}) async {
|
||||
await post("/meal/from_preset", {
|
||||
"name": name,
|
||||
"diary_id": diaryId,
|
||||
"preset_id": presetId,
|
||||
});
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> addProduct({
|
||||
required String name,
|
||||
required double protein,
|
||||
required double carb,
|
||||
required double fat,
|
||||
required double fiber,
|
||||
}) async {
|
||||
var response = await post("/product", {
|
||||
"name": name,
|
||||
"protein": protein,
|
||||
"carb": carb,
|
||||
"fat": fat,
|
||||
"fiber": fiber,
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<void> saveMeal(Meal meal, String name) async {
|
||||
await postNoResult("/meal/${meal.id}/save", {
|
||||
"name": name,
|
||||
});
|
||||
return Client(
|
||||
api: api,
|
||||
product: ProductClient(apiClient: api),
|
||||
preset: PresetClient(apiClient: api),
|
||||
meal: MealClient(apiClient: api),
|
||||
diary: DiaryClient(apiClient: api),
|
||||
entry: EntryClient(apiClient: api));
|
||||
}
|
||||
}
|
||||
|
|
219
lib/client/api.dart
Normal file
219
lib/client/api.dart
Normal file
|
@ -0,0 +1,219 @@
|
|||
import 'package:http/http.dart' as http;
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
|
||||
class ApiClient {
|
||||
final String baseUrl;
|
||||
String? token;
|
||||
String? refreshToken;
|
||||
http.Client httpClient = http.Client();
|
||||
final FlutterSecureStorage storage = const FlutterSecureStorage();
|
||||
|
||||
ApiClient({
|
||||
required this.baseUrl,
|
||||
});
|
||||
|
||||
static Future<ApiClient> create({required String baseUrl}) async {
|
||||
var client = ApiClient(baseUrl: baseUrl);
|
||||
client.loadToken();
|
||||
return client;
|
||||
}
|
||||
|
||||
Future<void> loadToken() async {
|
||||
Map<String, String> allValues = await storage.readAll();
|
||||
|
||||
if (allValues.containsKey('token')) {
|
||||
token = allValues['token'];
|
||||
}
|
||||
if (allValues.containsKey('refreshToken')) {
|
||||
refreshToken = allValues['refreshToken'];
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, String> headers({bool forGet = false, bool forLogin = false}) {
|
||||
if (token == null && !forLogin) {
|
||||
throw Exception('Not logged in');
|
||||
}
|
||||
|
||||
final headers = {
|
||||
'Accept': 'application/json',
|
||||
};
|
||||
|
||||
if (!forGet) {
|
||||
headers['Content-Type'] = 'application/json';
|
||||
}
|
||||
|
||||
if (token != null) {
|
||||
headers['Authorization'] = 'Bearer $token';
|
||||
}
|
||||
|
||||
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 {
|
||||
final response = await httpClient.get(
|
||||
Uri.parse('$baseUrl$path'),
|
||||
headers: headers(forGet: true),
|
||||
);
|
||||
|
||||
if (response.statusCode == 401) {
|
||||
await refresh();
|
||||
return await get(path);
|
||||
}
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Response returned status code: ${response.statusCode}');
|
||||
}
|
||||
|
||||
return _jsonDecode(response);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> post(String path, Map<String, dynamic> body,
|
||||
{bool forLogin = 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 post(path, body, forLogin: forLogin);
|
||||
}
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Response returned status code: ${response.statusCode}');
|
||||
}
|
||||
|
||||
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 {
|
||||
final response = await httpClient.delete(
|
||||
Uri.parse('$baseUrl$path'),
|
||||
headers: headers(),
|
||||
);
|
||||
|
||||
if (response.statusCode == 401) {
|
||||
await refresh();
|
||||
return await delete(path);
|
||||
}
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Response returned status code: ${response.statusCode}');
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> patch(
|
||||
String path, Map<String, dynamic> body) async {
|
||||
final response = await httpClient.patch(
|
||||
Uri.parse('$baseUrl$path'),
|
||||
body: jsonEncode(body),
|
||||
headers: headers(),
|
||||
);
|
||||
|
||||
if (response.statusCode == 401) {
|
||||
await refresh();
|
||||
return await patch(path, body);
|
||||
}
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Response returned status code: ${response.statusCode}');
|
||||
}
|
||||
|
||||
return _jsonDecode(response);
|
||||
}
|
||||
|
||||
Future<void> login(String username, String password) async {
|
||||
final headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Accept': 'application/json',
|
||||
};
|
||||
|
||||
final response = await httpClient.post(
|
||||
Uri.parse('$baseUrl/token'),
|
||||
body: {
|
||||
'username': username,
|
||||
'password': password,
|
||||
},
|
||||
encoding: Encoding.getByName('utf-8'),
|
||||
headers: headers,
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Failed to login');
|
||||
}
|
||||
|
||||
final token = _jsonDecode(response)['access_token'];
|
||||
this.token = token;
|
||||
await storage.write(key: 'token', value: token);
|
||||
|
||||
final refreshToken = _jsonDecode(response)['refresh_token'];
|
||||
this.refreshToken = refreshToken;
|
||||
await storage.write(key: 'refreshToken', value: refreshToken);
|
||||
}
|
||||
|
||||
Future<void> refresh() async {
|
||||
if (refreshToken == null) {
|
||||
throw Exception("No valid refresh token found");
|
||||
}
|
||||
|
||||
final response = await post("/token/refresh", {
|
||||
"refresh_token": refreshToken,
|
||||
});
|
||||
|
||||
token = response['access_token'] as String;
|
||||
await storage.write(key: 'token', value: token);
|
||||
|
||||
refreshToken = response['refresh_token'] as String;
|
||||
await storage.write(key: 'refreshToken', value: refreshToken);
|
||||
}
|
||||
|
||||
Future<void> logout() async {
|
||||
token = null;
|
||||
refreshToken = null;
|
||||
|
||||
await storage.deleteAll();
|
||||
}
|
||||
|
||||
Future<void> register(String username, String password) async {
|
||||
try {
|
||||
await post(
|
||||
"/user",
|
||||
{
|
||||
"username": username,
|
||||
"password": password,
|
||||
},
|
||||
forLogin: true,
|
||||
);
|
||||
} catch (e) {
|
||||
throw Exception("Failed to register");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1 +1,7 @@
|
|||
import 'package:fooder/client.dart';
|
||||
import 'package:fooder/client/api.dart';
|
||||
|
||||
abstract class BasedClient {
|
||||
final ApiClient apiClient;
|
||||
|
||||
const BasedClient({required this.apiClient});
|
||||
}
|
||||
|
|
18
lib/client/diary.dart
Normal file
18
lib/client/diary.dart
Normal file
|
@ -0,0 +1,18 @@
|
|||
import 'package:fooder/client/based.dart';
|
||||
import 'package:fooder/models/diary.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class DiaryClient extends BasedClient {
|
||||
const DiaryClient({required super.apiClient});
|
||||
|
||||
Future<Diary> get({required DateTime date}) async {
|
||||
var formatter = DateFormat('yyyy-MM-dd');
|
||||
var params = {
|
||||
"date": formatter.format(date),
|
||||
};
|
||||
var response =
|
||||
await apiClient.get("/diary?${Uri(queryParameters: params).query}");
|
||||
|
||||
return Diary.fromJson(response);
|
||||
}
|
||||
}
|
36
lib/client/entry.dart
Normal file
36
lib/client/entry.dart
Normal file
|
@ -0,0 +1,36 @@
|
|||
import 'package:fooder/client/based.dart';
|
||||
|
||||
class EntryClient extends BasedClient {
|
||||
const EntryClient({required super.apiClient});
|
||||
|
||||
Future<void> create({
|
||||
required double grams,
|
||||
required int productId,
|
||||
required int mealId,
|
||||
}) async {
|
||||
var entry = {
|
||||
"grams": grams,
|
||||
"product_id": productId,
|
||||
"meal_id": mealId,
|
||||
};
|
||||
await apiClient.post("/entry", entry);
|
||||
}
|
||||
|
||||
Future<void> delete(int id) async {
|
||||
await apiClient.delete("/entry/$id");
|
||||
}
|
||||
|
||||
Future<void> update(
|
||||
int id, {
|
||||
required double grams,
|
||||
required int productId,
|
||||
required int mealId,
|
||||
}) async {
|
||||
var entry = {
|
||||
"grams": grams,
|
||||
"product_id": productId,
|
||||
"meal_id": mealId,
|
||||
};
|
||||
await apiClient.patch("/entry/$id", entry);
|
||||
}
|
||||
}
|
33
lib/client/meal.dart
Normal file
33
lib/client/meal.dart
Normal file
|
@ -0,0 +1,33 @@
|
|||
import 'package:fooder/client/based.dart';
|
||||
|
||||
class MealClient extends BasedClient {
|
||||
const MealClient({required super.apiClient});
|
||||
|
||||
Future<void> create({required String name, required int diaryId}) async {
|
||||
await apiClient.post("/meal", {
|
||||
"name": name,
|
||||
"diary_id": diaryId,
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> createFromPreset(
|
||||
{required String name,
|
||||
required int diaryId,
|
||||
required int presetId}) async {
|
||||
await apiClient.post("/meal/from_preset", {
|
||||
"name": name,
|
||||
"diary_id": diaryId,
|
||||
"preset_id": presetId,
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> update(int id, String name) async {
|
||||
await apiClient.postNoResult("/meal/$id/save", {
|
||||
"name": name,
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> delete(int id) async {
|
||||
await apiClient.delete("/meal/$id");
|
||||
}
|
||||
}
|
18
lib/client/preset.dart
Normal file
18
lib/client/preset.dart
Normal file
|
@ -0,0 +1,18 @@
|
|||
import 'package:fooder/client/based.dart';
|
||||
import 'package:fooder/models/preset.dart';
|
||||
|
||||
class PresetClient extends BasedClient {
|
||||
const PresetClient({required super.apiClient});
|
||||
|
||||
Future<List<Preset>> list(String? q) async {
|
||||
var response =
|
||||
await apiClient.get("/preset?${Uri(queryParameters: {"q": q}).query}");
|
||||
return (response['presets'] as List<dynamic>)
|
||||
.map((e) => Preset.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<void> delete(int id) async {
|
||||
await apiClient.delete("/preset/$id");
|
||||
}
|
||||
}
|
41
lib/client/product.dart
Normal file
41
lib/client/product.dart
Normal file
|
@ -0,0 +1,41 @@
|
|||
import 'package:fooder/client/based.dart';
|
||||
import 'package:fooder/models/product.dart';
|
||||
|
||||
class ProductClient extends BasedClient {
|
||||
const ProductClient({required super.apiClient});
|
||||
|
||||
Future<List<Product>> list(String q) async {
|
||||
var response =
|
||||
await apiClient.get("/product?${Uri(queryParameters: {"q": q}).query}");
|
||||
|
||||
return (response['products'] as List<dynamic>)
|
||||
.map((e) => Product.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<Product> getByBarcode(String barcode) async {
|
||||
var response = await apiClient.get(
|
||||
"/product/by_barcode?${Uri(queryParameters: {
|
||||
"barcode": barcode
|
||||
}).query}");
|
||||
|
||||
return Product.fromJson(response);
|
||||
}
|
||||
|
||||
Future<Product> create({
|
||||
required String name,
|
||||
required double protein,
|
||||
required double carb,
|
||||
required double fat,
|
||||
required double fiber,
|
||||
}) async {
|
||||
var response = await apiClient.post("/product", {
|
||||
"name": name,
|
||||
"protein": protein,
|
||||
"carb": carb,
|
||||
"fat": fat,
|
||||
"fiber": fiber,
|
||||
});
|
||||
return Product.fromJson(response);
|
||||
}
|
||||
}
|
16
lib/context.dart
Normal file
16
lib/context.dart
Normal file
|
@ -0,0 +1,16 @@
|
|||
import 'package:fooder/client.dart';
|
||||
import 'package:fooder/storage.dart';
|
||||
|
||||
class Context {
|
||||
final Client client;
|
||||
final Storage storage;
|
||||
|
||||
Context({required this.client, required this.storage});
|
||||
|
||||
static Future<Context> create({required String baseUrl}) async {
|
||||
var client = await Client.create(baseUrl: baseUrl);
|
||||
var storage = await Storage.create();
|
||||
|
||||
return Context(client: client, storage: storage);
|
||||
}
|
||||
}
|
|
@ -1,14 +1,12 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:fooder/screens/login.dart';
|
||||
import 'package:fooder/client.dart';
|
||||
import 'package:fooder/storage.dart';
|
||||
import 'package:fooder/context.dart';
|
||||
import 'package:fooder/theme.dart';
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
final Storage storage;
|
||||
final ApiClient apiClient;
|
||||
final Context ctx;
|
||||
|
||||
const MyApp({required this.storage, required this.apiClient, super.key});
|
||||
const MyApp({required this.ctx, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -19,18 +17,16 @@ class MyApp extends StatelessWidget {
|
|||
themeMode: ThemeMode.system,
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: LoginScreen(
|
||||
apiClient: apiClient,
|
||||
storage: storage,
|
||||
ctx: ctx,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void main() async {
|
||||
var storage = await Storage.create();
|
||||
var apiClient = await ApiClient.create(
|
||||
var ctx = await Context.create(
|
||||
baseUrl: 'https://fooderapi.domandoman.xyz/api',
|
||||
);
|
||||
|
||||
runApp(MyApp(storage: storage, apiClient: apiClient));
|
||||
runApp(MyApp(ctx: ctx));
|
||||
}
|
||||
|
|
|
@ -14,11 +14,7 @@ import 'package:simple_barcode_scanner/simple_barcode_scanner.dart';
|
|||
class AddEntryScreen extends BasedScreen {
|
||||
final Diary diary;
|
||||
|
||||
const AddEntryScreen(
|
||||
{super.key,
|
||||
required super.apiClient,
|
||||
required super.storage,
|
||||
required this.diary});
|
||||
const AddEntryScreen({super.key, required super.ctx, required this.diary});
|
||||
|
||||
@override
|
||||
State<AddEntryScreen> createState() => _AddEntryScreen();
|
||||
|
@ -54,14 +50,10 @@ class _AddEntryScreen extends BasedState<AddEntryScreen> {
|
|||
}
|
||||
|
||||
Future<void> _getProducts() async {
|
||||
var productsMap = await apiClient.getProducts(productNameController.text);
|
||||
|
||||
var parsedProducts = (productsMap['products'] as List<dynamic>)
|
||||
.map((e) => Product.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
var products = await client.product.list(productNameController.text);
|
||||
|
||||
setState(() {
|
||||
products = parsedProducts;
|
||||
this.products = products;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -99,7 +91,7 @@ class _AddEntryScreen extends BasedState<AddEntryScreen> {
|
|||
return;
|
||||
}
|
||||
|
||||
await apiClient.addEntry(
|
||||
await client.entry.create(
|
||||
grams: grams,
|
||||
productId: products[0].id,
|
||||
mealId: meal!.id,
|
||||
|
@ -117,9 +109,7 @@ class _AddEntryScreen extends BasedState<AddEntryScreen> {
|
|||
|
||||
if (res is String) {
|
||||
try {
|
||||
var productMap = await apiClient.getProductByBarcode(res);
|
||||
|
||||
var product = Product.fromJson(productMap);
|
||||
var product = await client.product.getByBarcode(res);
|
||||
|
||||
setState(() {
|
||||
products = [product];
|
||||
|
@ -185,8 +175,7 @@ class _AddEntryScreen extends BasedState<AddEntryScreen> {
|
|||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => AddProductScreen(
|
||||
apiClient: apiClient,
|
||||
storage: storage,
|
||||
ctx: ctx,
|
||||
),
|
||||
),
|
||||
).then((product) {
|
||||
|
|
|
@ -9,11 +9,7 @@ import 'package:fooder/components/floating_action_button.dart';
|
|||
class AddMealScreen extends BasedScreen {
|
||||
final Diary diary;
|
||||
|
||||
const AddMealScreen(
|
||||
{super.key,
|
||||
required super.apiClient,
|
||||
required super.storage,
|
||||
required this.diary});
|
||||
const AddMealScreen({super.key, required super.ctx, required this.diary});
|
||||
|
||||
@override
|
||||
State<AddMealScreen> createState() => _AddMealScreen();
|
||||
|
@ -28,12 +24,10 @@ class _AddMealScreen extends BasedState<AddMealScreen> {
|
|||
Diary get diary => widget.diary;
|
||||
|
||||
Future<void> _getPresets() async {
|
||||
var presetsMap = await apiClient.getPresets(presetNameController.text);
|
||||
var presets = await client.preset.list(presetNameController.text);
|
||||
|
||||
setState(() {
|
||||
presets = (presetsMap['presets'] as List<dynamic>)
|
||||
.map((e) => Preset.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
this.presets = presets;
|
||||
selectedPreset = null;
|
||||
});
|
||||
}
|
||||
|
@ -59,22 +53,22 @@ class _AddMealScreen extends BasedState<AddMealScreen> {
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> _addMeal() async {
|
||||
await apiClient.addMeal(
|
||||
Future<void> addMeal() async {
|
||||
await client.meal.create(
|
||||
name: nameController.text,
|
||||
diaryId: diary.id,
|
||||
);
|
||||
popMeDaddy();
|
||||
}
|
||||
|
||||
Future<void> _deletePreset(Preset preset) async {
|
||||
apiClient.deletePreset(preset.id);
|
||||
Future<void> deletePreset(Preset preset) async {
|
||||
client.preset.delete(preset.id);
|
||||
setState(() {
|
||||
presets.remove(preset);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> deletePreset(context, Preset preset) async {
|
||||
Future<void> deletePresetPopup(context, Preset preset) async {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
|
@ -90,7 +84,7 @@ class _AddMealScreen extends BasedState<AddMealScreen> {
|
|||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () {
|
||||
_deletePreset(preset);
|
||||
deletePreset(preset);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
|
@ -100,13 +94,13 @@ class _AddMealScreen extends BasedState<AddMealScreen> {
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> _addMealFromPreset() async {
|
||||
Future<void> addMealFromPreset() async {
|
||||
if (selectedPreset == null) {
|
||||
_addMeal();
|
||||
addMeal();
|
||||
return;
|
||||
}
|
||||
|
||||
await apiClient.addMealFromPreset(
|
||||
await client.meal.createFromPreset(
|
||||
name: nameChanged ? nameController.text : selectedPreset!.name,
|
||||
diaryId: diary.id,
|
||||
presetId: selectedPreset!.id,
|
||||
|
@ -143,10 +137,10 @@ class _AddMealScreen extends BasedState<AddMealScreen> {
|
|||
presetNameController.text = preset.name;
|
||||
selectedPreset = preset;
|
||||
});
|
||||
_addMealFromPreset();
|
||||
addMealFromPreset();
|
||||
},
|
||||
onLongPress: () {
|
||||
deletePreset(context, preset);
|
||||
deletePresetPopup(context, preset);
|
||||
},
|
||||
title: PresetWidget(
|
||||
preset: preset,
|
||||
|
@ -156,7 +150,7 @@ class _AddMealScreen extends BasedState<AddMealScreen> {
|
|||
),
|
||||
),
|
||||
floatingActionButton: FActionButton(
|
||||
onPressed: _addMealFromPreset,
|
||||
onPressed: addMealFromPreset,
|
||||
icon: Icons.playlist_add_rounded,
|
||||
),
|
||||
);
|
||||
|
|
|
@ -7,8 +7,7 @@ import 'package:fooder/components/text.dart';
|
|||
import 'package:fooder/components/floating_action_button.dart';
|
||||
|
||||
class AddProductScreen extends BasedScreen {
|
||||
const AddProductScreen(
|
||||
{super.key, required super.apiClient, required super.storage});
|
||||
const AddProductScreen({super.key, required super.ctx});
|
||||
|
||||
@override
|
||||
State<AddProductScreen> createState() => _AddProductScreen();
|
||||
|
@ -38,38 +37,25 @@ class _AddProductScreen extends BasedState<AddProductScreen> {
|
|||
);
|
||||
}
|
||||
|
||||
Future<double?> _parseDouble(String text, String name,
|
||||
{bool silent = false}) async {
|
||||
try {
|
||||
return double.parse(text.replaceAll(",", "."));
|
||||
} catch (e) {
|
||||
if (!silent) {
|
||||
showError("$name must be a number");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _addProduct() async {
|
||||
var carb = await _parseDouble(carbController.text, "Carbs");
|
||||
var fat = await _parseDouble(fatController.text, "Fat");
|
||||
var protein = await _parseDouble(proteinController.text, "Protein");
|
||||
Future<void> addProduct() async {
|
||||
var carb = await parseDouble(carbController.text, "Carbs");
|
||||
var fat = await parseDouble(fatController.text, "Fat");
|
||||
var protein = await parseDouble(proteinController.text, "Protein");
|
||||
var fiber =
|
||||
await _parseDouble(fiberController.text, "Fiber", silent: true) ?? 0;
|
||||
await parseDouble(fiberController.text, "Fiber", silent: true) ?? 0;
|
||||
|
||||
if (carb == null || fat == null || protein == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var productJson = await apiClient.addProduct(
|
||||
var product = await client.product.create(
|
||||
carb: carb,
|
||||
fat: fat,
|
||||
protein: protein,
|
||||
fiber: fiber,
|
||||
name: nameController.text,
|
||||
);
|
||||
var product = Product.fromJson(productJson);
|
||||
popMeDaddy(product);
|
||||
} catch (e) {
|
||||
showError(
|
||||
|
@ -195,7 +181,7 @@ class _AddProductScreen extends BasedState<AddProductScreen> {
|
|||
])),
|
||||
),
|
||||
floatingActionButton: FActionButton(
|
||||
onPressed: _addProduct,
|
||||
onPressed: addProduct,
|
||||
icon: Icons.save,
|
||||
),
|
||||
);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:fooder/context.dart';
|
||||
import 'package:fooder/client.dart';
|
||||
import 'package:fooder/storage.dart';
|
||||
import 'package:fooder/components/app_bar.dart';
|
||||
|
@ -13,19 +14,18 @@ TextStyle logoStyle(context) {
|
|||
}
|
||||
|
||||
abstract class BasedScreen extends StatefulWidget {
|
||||
final ApiClient apiClient;
|
||||
final Storage storage;
|
||||
final Context ctx;
|
||||
|
||||
const BasedScreen(
|
||||
{super.key, required this.apiClient, required this.storage});
|
||||
const BasedScreen({super.key, required this.ctx});
|
||||
}
|
||||
|
||||
abstract class BasedState<T extends BasedScreen> extends State<T> {
|
||||
ApiClient get apiClient => widget.apiClient;
|
||||
Storage get storage => widget.storage;
|
||||
Context get ctx => widget.ctx;
|
||||
Client get client => widget.ctx.client;
|
||||
Storage get storage => widget.ctx.storage;
|
||||
|
||||
void logout() async {
|
||||
await apiClient.logout();
|
||||
await client.api.logout();
|
||||
backToLogin();
|
||||
}
|
||||
|
||||
|
@ -33,8 +33,7 @@ abstract class BasedState<T extends BasedScreen> extends State<T> {
|
|||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
LoginScreen(apiClient: apiClient, storage: storage),
|
||||
builder: (context) => LoginScreen(ctx: ctx),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -43,8 +42,7 @@ abstract class BasedState<T extends BasedScreen> extends State<T> {
|
|||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
MainScreen(apiClient: apiClient, storage: storage),
|
||||
builder: (context) => MainScreen(ctx: ctx),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -103,6 +101,18 @@ abstract class BasedState<T extends BasedScreen> extends State<T> {
|
|||
);
|
||||
}
|
||||
|
||||
Future<double?> parseDouble(String text, String name,
|
||||
{bool silent = false}) async {
|
||||
try {
|
||||
return double.parse(text.replaceAll(",", "."));
|
||||
} catch (e) {
|
||||
if (!silent) {
|
||||
showError("$name must be a number");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void showError(String message) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
|
|
|
@ -12,11 +12,7 @@ import 'package:simple_barcode_scanner/simple_barcode_scanner.dart';
|
|||
class EditEntryScreen extends BasedScreen {
|
||||
final Entry entry;
|
||||
|
||||
const EditEntryScreen(
|
||||
{super.key,
|
||||
required super.apiClient,
|
||||
required super.storage,
|
||||
required this.entry});
|
||||
const EditEntryScreen({super.key, required super.ctx, required this.entry});
|
||||
|
||||
@override
|
||||
State<EditEntryScreen> createState() => _EditEntryScreen();
|
||||
|
@ -26,7 +22,7 @@ class _EditEntryScreen extends BasedState<EditEntryScreen> {
|
|||
final gramsController = TextEditingController();
|
||||
final productNameController = TextEditingController();
|
||||
List<Product> products = [];
|
||||
Entry get entry => entry;
|
||||
Entry get entry => widget.entry;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
|
@ -49,36 +45,25 @@ class _EditEntryScreen extends BasedState<EditEntryScreen> {
|
|||
});
|
||||
}
|
||||
|
||||
Future<void> _getProducts() async {
|
||||
var productsMap = await apiClient.getProducts(productNameController.text);
|
||||
Future<void> getProducts() async {
|
||||
var products = await client.product.list(productNameController.text);
|
||||
setState(() {
|
||||
products = (productsMap['products'] as List<dynamic>)
|
||||
.map((e) => Product.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
this.products = products;
|
||||
});
|
||||
}
|
||||
|
||||
Future<double?> _parseDouble(String text, String name) async {
|
||||
try {
|
||||
return double.parse(text.replaceAll(",", "."));
|
||||
} catch (e) {
|
||||
showError("$name must be a number");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _saveEntry() async {
|
||||
Future<void> saveEntry() async {
|
||||
if (products.length != 1) {
|
||||
showError("Pick product first");
|
||||
return;
|
||||
}
|
||||
|
||||
var grams = await _parseDouble(gramsController.text, "Grams");
|
||||
var grams = await parseDouble(gramsController.text, "Grams");
|
||||
if (grams == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
await apiClient.updateEntry(
|
||||
await client.entry.update(
|
||||
entry.id,
|
||||
grams: grams,
|
||||
productId: products[0].id,
|
||||
|
@ -87,12 +72,12 @@ class _EditEntryScreen extends BasedState<EditEntryScreen> {
|
|||
popMeDaddy();
|
||||
}
|
||||
|
||||
Future<void> _deleteEntry() async {
|
||||
await apiClient.deleteEntry(widget.entry.id);
|
||||
Future<void> deleteEntry() async {
|
||||
await client.entry.delete(widget.entry.id);
|
||||
popMeDaddy();
|
||||
}
|
||||
|
||||
Future<void> _findProductByBarCode() async {
|
||||
Future<void> findProductByBarCode() async {
|
||||
var res = await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
|
@ -102,9 +87,7 @@ class _EditEntryScreen extends BasedState<EditEntryScreen> {
|
|||
|
||||
if (res is String) {
|
||||
try {
|
||||
var productMap = await apiClient.getProductByBarcode(res);
|
||||
|
||||
var product = Product.fromJson(productMap);
|
||||
var product = await client.product.getByBarcode(res);
|
||||
|
||||
setState(() {
|
||||
products = [product];
|
||||
|
@ -128,7 +111,7 @@ class _EditEntryScreen extends BasedState<EditEntryScreen> {
|
|||
FTextInput(
|
||||
labelText: 'Product name',
|
||||
controller: productNameController,
|
||||
onChanged: (_) => _getProducts(),
|
||||
onChanged: (_) => getProducts(),
|
||||
autofocus: true,
|
||||
),
|
||||
FTextInput(
|
||||
|
@ -147,8 +130,7 @@ class _EditEntryScreen extends BasedState<EditEntryScreen> {
|
|||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => AddProductScreen(
|
||||
apiClient: apiClient,
|
||||
storage: storage,
|
||||
ctx: ctx,
|
||||
),
|
||||
),
|
||||
).then((product) {
|
||||
|
@ -181,18 +163,18 @@ class _EditEntryScreen extends BasedState<EditEntryScreen> {
|
|||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
FActionButton(
|
||||
onPressed: _findProductByBarCode,
|
||||
onPressed: findProductByBarCode,
|
||||
icon: Icons.photo_camera,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
FActionButton(
|
||||
onPressed: _deleteEntry,
|
||||
onPressed: deleteEntry,
|
||||
tag: "fap1",
|
||||
icon: Icons.delete,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
FActionButton(
|
||||
onPressed: _saveEntry,
|
||||
onPressed: saveEntry,
|
||||
tag: "fap2",
|
||||
icon: Icons.save,
|
||||
),
|
||||
|
|
|
@ -7,8 +7,7 @@ import 'package:fooder/components/text.dart';
|
|||
import 'package:fooder/components/button.dart';
|
||||
|
||||
class LoginScreen extends BasedScreen {
|
||||
const LoginScreen(
|
||||
{super.key, required super.apiClient, required super.storage});
|
||||
const LoginScreen({super.key, required super.ctx});
|
||||
|
||||
@override
|
||||
State<LoginScreen> createState() => _LoginScreen();
|
||||
|
@ -29,16 +28,15 @@ class _LoginScreen extends BasedState<LoginScreen> {
|
|||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
MainScreen(apiClient: apiClient, storage: storage),
|
||||
builder: (context) => MainScreen(ctx: ctx),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// login client when button pressed
|
||||
Future<void> _login() async {
|
||||
Future<void> login() async {
|
||||
try {
|
||||
await apiClient.login(
|
||||
await client.api.login(
|
||||
usernameController.text,
|
||||
passwordController.text,
|
||||
);
|
||||
|
@ -60,18 +58,18 @@ class _LoginScreen extends BasedState<LoginScreen> {
|
|||
AutofillHints.password,
|
||||
],
|
||||
});
|
||||
_asyncInitState().then((value) => null);
|
||||
asyncInitState().then((value) => null);
|
||||
}
|
||||
|
||||
Future<void> _asyncInitState() async {
|
||||
await apiClient.loadToken();
|
||||
Future<void> asyncInitState() async {
|
||||
await client.api.loadToken();
|
||||
|
||||
if (apiClient.refreshToken == null) {
|
||||
if (client.api.refreshToken == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await apiClient.refresh();
|
||||
await client.api.refresh();
|
||||
showText("Welcome back!");
|
||||
popMeDaddy();
|
||||
} on Exception catch (_) {
|
||||
|
@ -107,13 +105,13 @@ class _LoginScreen extends BasedState<LoginScreen> {
|
|||
FTextInput(
|
||||
labelText: 'Password',
|
||||
controller: passwordController,
|
||||
onFieldSubmitted: (_) => _login(),
|
||||
onFieldSubmitted: (_) => login(),
|
||||
autofillHints: const [AutofillHints.password],
|
||||
obscureText: true,
|
||||
),
|
||||
FButton(
|
||||
labelText: 'Sign In',
|
||||
onPressed: _login,
|
||||
onPressed: login,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
|
@ -123,8 +121,7 @@ class _LoginScreen extends BasedState<LoginScreen> {
|
|||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => RegisterScreen(
|
||||
apiClient: apiClient,
|
||||
storage: storage,
|
||||
ctx: ctx,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -10,8 +10,7 @@ import 'package:fooder/components/date_picker.dart';
|
|||
import 'package:fooder/components/floating_action_button.dart';
|
||||
|
||||
class MainScreen extends BasedScreen {
|
||||
const MainScreen(
|
||||
{super.key, required super.apiClient, required super.storage});
|
||||
const MainScreen({super.key, required super.ctx});
|
||||
|
||||
@override
|
||||
State<MainScreen> createState() => _MainScreen();
|
||||
|
@ -28,10 +27,10 @@ class _MainScreen extends BasedState<MainScreen> {
|
|||
}
|
||||
|
||||
Future<void> _asyncInitState() async {
|
||||
var diaryMap = await apiClient.getDiary(date: date);
|
||||
var diary = await client.diary.get(date: date);
|
||||
|
||||
setState(() {
|
||||
diary = Diary.fromJson(diaryMap);
|
||||
this.diary = diary;
|
||||
date = date;
|
||||
});
|
||||
}
|
||||
|
@ -52,8 +51,7 @@ class _MainScreen extends BasedState<MainScreen> {
|
|||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => AddEntryScreen(
|
||||
apiClient: apiClient, diary: diary!, storage: storage),
|
||||
builder: (context) => AddEntryScreen(ctx: ctx, diary: diary!),
|
||||
),
|
||||
).then((_) => _asyncInitState());
|
||||
}
|
||||
|
@ -67,9 +65,8 @@ class _MainScreen extends BasedState<MainScreen> {
|
|||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => AddMealScreen(
|
||||
apiClient: apiClient,
|
||||
ctx: ctx,
|
||||
diary: diary!,
|
||||
storage: storage,
|
||||
),
|
||||
),
|
||||
).then((_) => _asyncInitState());
|
||||
|
@ -91,13 +88,11 @@ class _MainScreen extends BasedState<MainScreen> {
|
|||
[
|
||||
SummaryWidget(
|
||||
diary: diary!,
|
||||
apiClient: apiClient,
|
||||
refreshParent: _asyncInitState,
|
||||
),
|
||||
for (var (i, meal) in diary!.meals.indexed)
|
||||
MealWidget(
|
||||
apiClient: apiClient,
|
||||
storage: storage,
|
||||
ctx: ctx,
|
||||
meal: meal,
|
||||
refreshParent: _asyncInitState,
|
||||
initiallyExpanded: i == 0,
|
||||
|
|
|
@ -5,8 +5,7 @@ import 'package:fooder/components/text.dart';
|
|||
import 'package:fooder/components/button.dart';
|
||||
|
||||
class RegisterScreen extends BasedScreen {
|
||||
const RegisterScreen(
|
||||
{super.key, required super.apiClient, required super.storage});
|
||||
const RegisterScreen({super.key, required super.ctx});
|
||||
|
||||
@override
|
||||
State<RegisterScreen> createState() => _RegisterScreen();
|
||||
|
@ -50,7 +49,7 @@ class _RegisterScreen extends BasedState<RegisterScreen> {
|
|||
}
|
||||
|
||||
try {
|
||||
await apiClient.register(
|
||||
await client.api.register(
|
||||
usernameController.text,
|
||||
passwordController.text,
|
||||
);
|
||||
|
|
|
@ -3,8 +3,7 @@ import 'package:fooder/models/meal.dart';
|
|||
import 'package:fooder/widgets/entry.dart';
|
||||
import 'package:fooder/widgets/macro.dart';
|
||||
import 'package:fooder/screens/edit_entry.dart';
|
||||
import 'package:fooder/client.dart';
|
||||
import 'package:fooder/storage.dart';
|
||||
import 'package:fooder/context.dart';
|
||||
import 'dart:core';
|
||||
|
||||
class MealHeader extends StatelessWidget {
|
||||
|
@ -38,8 +37,7 @@ class MealWidget extends StatelessWidget {
|
|||
static const maxWidth = 920.0;
|
||||
|
||||
final Meal meal;
|
||||
final ApiClient apiClient;
|
||||
final Storage storage;
|
||||
final Context ctx;
|
||||
final Function() refreshParent;
|
||||
final Function(String) showText;
|
||||
final bool initiallyExpanded;
|
||||
|
@ -47,11 +45,10 @@ class MealWidget extends StatelessWidget {
|
|||
const MealWidget({
|
||||
super.key,
|
||||
required this.meal,
|
||||
required this.apiClient,
|
||||
required this.ctx,
|
||||
required this.refreshParent,
|
||||
required this.initiallyExpanded,
|
||||
required this.showText,
|
||||
required this.storage,
|
||||
});
|
||||
|
||||
Future<void> saveMeal(context) async {
|
||||
|
@ -77,7 +74,7 @@ class MealWidget extends StatelessWidget {
|
|||
IconButton(
|
||||
icon: const Icon(Icons.save),
|
||||
onPressed: () {
|
||||
apiClient.saveMeal(meal, textFieldController.text);
|
||||
ctx.client.meal.update(meal.id, textFieldController.text);
|
||||
Navigator.pop(context);
|
||||
showText("Meal saved");
|
||||
},
|
||||
|
@ -88,11 +85,11 @@ class MealWidget extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> _deleteMeal(Meal meal) async {
|
||||
await apiClient.deleteMeal(meal.id);
|
||||
Future<void> deleteMeal(Meal meal) async {
|
||||
await ctx.client.meal.delete(meal.id);
|
||||
}
|
||||
|
||||
Future<void> deleteMeal(context) async {
|
||||
Future<void> deleteMealPopup(context) async {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
|
@ -109,7 +106,7 @@ class MealWidget extends StatelessWidget {
|
|||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () {
|
||||
_deleteMeal(meal).then((_) => refreshParent());
|
||||
deleteMeal(meal).then((_) => refreshParent());
|
||||
Navigator.pop(context);
|
||||
showText("Meal deleted");
|
||||
},
|
||||
|
@ -125,9 +122,8 @@ class MealWidget extends StatelessWidget {
|
|||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => EditEntryScreen(
|
||||
apiClient: apiClient,
|
||||
ctx: ctx,
|
||||
entry: entry,
|
||||
storage: storage,
|
||||
),
|
||||
),
|
||||
).then((_) => refreshParent());
|
||||
|
@ -195,7 +191,7 @@ class MealWidget extends StatelessWidget {
|
|||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () => deleteMeal(context),
|
||||
onPressed: () => deleteMealPopup(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:fooder/models/diary.dart';
|
||||
import 'package:fooder/widgets/macro.dart';
|
||||
import 'package:fooder/client.dart';
|
||||
import 'dart:core';
|
||||
|
||||
class SummaryHeader extends StatelessWidget {
|
||||
|
@ -33,14 +32,10 @@ class SummaryWidget extends StatelessWidget {
|
|||
static const maxWidth = 920.0;
|
||||
|
||||
final Diary diary;
|
||||
final ApiClient apiClient;
|
||||
final Function() refreshParent;
|
||||
|
||||
const SummaryWidget(
|
||||
{super.key,
|
||||
required this.diary,
|
||||
required this.apiClient,
|
||||
required this.refreshParent});
|
||||
{super.key, required this.diary, required this.refreshParent});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
Loading…
Reference in a new issue