[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 'package:fooder/client/api.dart';
 | 
				
			||||||
import 'dart:convert';
 | 
					import 'package:fooder/client/product.dart';
 | 
				
			||||||
import 'package:intl/intl.dart';
 | 
					import 'package:fooder/client/entry.dart';
 | 
				
			||||||
import 'package:fooder/models/meal.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 {
 | 
					  Client(
 | 
				
			||||||
  final String baseUrl;
 | 
					      {required this.api,
 | 
				
			||||||
  String? token;
 | 
					      required this.product,
 | 
				
			||||||
  String? refreshToken;
 | 
					      required this.entry,
 | 
				
			||||||
  http.Client httpClient = http.Client();
 | 
					      required this.meal,
 | 
				
			||||||
  final FlutterSecureStorage storage = const FlutterSecureStorage();
 | 
					      required this.diary,
 | 
				
			||||||
 | 
					      required this.preset});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ApiClient({
 | 
					  static Future<Client> create({required String baseUrl}) async {
 | 
				
			||||||
    required this.baseUrl,
 | 
					    var api = await ApiClient.create(baseUrl: baseUrl);
 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static Future<ApiClient> create({required String baseUrl}) async {
 | 
					    return Client(
 | 
				
			||||||
    var client = ApiClient(baseUrl: baseUrl);
 | 
					        api: api,
 | 
				
			||||||
    client.loadToken();
 | 
					        product: ProductClient(apiClient: api),
 | 
				
			||||||
    return client;
 | 
					        preset: PresetClient(apiClient: api),
 | 
				
			||||||
  }
 | 
					        meal: MealClient(apiClient: api),
 | 
				
			||||||
 | 
					        diary: DiaryClient(apiClient: api),
 | 
				
			||||||
  Future<void> loadToken() async {
 | 
					        entry: EntryClient(apiClient: api));
 | 
				
			||||||
    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,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										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:flutter/material.dart';
 | 
				
			||||||
import 'package:fooder/screens/login.dart';
 | 
					import 'package:fooder/screens/login.dart';
 | 
				
			||||||
import 'package:fooder/client.dart';
 | 
					import 'package:fooder/context.dart';
 | 
				
			||||||
import 'package:fooder/storage.dart';
 | 
					 | 
				
			||||||
import 'package:fooder/theme.dart';
 | 
					import 'package:fooder/theme.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MyApp extends StatelessWidget {
 | 
					class MyApp extends StatelessWidget {
 | 
				
			||||||
  final Storage storage;
 | 
					  final Context ctx;
 | 
				
			||||||
  final ApiClient apiClient;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const MyApp({required this.storage, required this.apiClient, super.key});
 | 
					  const MyApp({required this.ctx, super.key});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
| 
						 | 
					@ -19,18 +17,16 @@ class MyApp extends StatelessWidget {
 | 
				
			||||||
      themeMode: ThemeMode.system,
 | 
					      themeMode: ThemeMode.system,
 | 
				
			||||||
      debugShowCheckedModeBanner: false,
 | 
					      debugShowCheckedModeBanner: false,
 | 
				
			||||||
      home: LoginScreen(
 | 
					      home: LoginScreen(
 | 
				
			||||||
        apiClient: apiClient,
 | 
					        ctx: ctx,
 | 
				
			||||||
        storage: storage,
 | 
					 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void main() async {
 | 
					void main() async {
 | 
				
			||||||
  var storage = await Storage.create();
 | 
					  var ctx = await Context.create(
 | 
				
			||||||
  var apiClient = await ApiClient.create(
 | 
					 | 
				
			||||||
    baseUrl: 'https://fooderapi.domandoman.xyz/api',
 | 
					    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 {
 | 
					class AddEntryScreen extends BasedScreen {
 | 
				
			||||||
  final Diary diary;
 | 
					  final Diary diary;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const AddEntryScreen(
 | 
					  const AddEntryScreen({super.key, required super.ctx, required this.diary});
 | 
				
			||||||
      {super.key,
 | 
					 | 
				
			||||||
      required super.apiClient,
 | 
					 | 
				
			||||||
      required super.storage,
 | 
					 | 
				
			||||||
      required this.diary});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  State<AddEntryScreen> createState() => _AddEntryScreen();
 | 
					  State<AddEntryScreen> createState() => _AddEntryScreen();
 | 
				
			||||||
| 
						 | 
					@ -54,14 +50,10 @@ class _AddEntryScreen extends BasedState<AddEntryScreen> {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<void> _getProducts() async {
 | 
					  Future<void> _getProducts() async {
 | 
				
			||||||
    var productsMap = await apiClient.getProducts(productNameController.text);
 | 
					    var products = await client.product.list(productNameController.text);
 | 
				
			||||||
 | 
					 | 
				
			||||||
    var parsedProducts = (productsMap['products'] as List<dynamic>)
 | 
					 | 
				
			||||||
        .map((e) => Product.fromJson(e as Map<String, dynamic>))
 | 
					 | 
				
			||||||
        .toList();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    setState(() {
 | 
					    setState(() {
 | 
				
			||||||
      products = parsedProducts;
 | 
					      this.products = products;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -99,7 +91,7 @@ class _AddEntryScreen extends BasedState<AddEntryScreen> {
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await apiClient.addEntry(
 | 
					    await client.entry.create(
 | 
				
			||||||
      grams: grams,
 | 
					      grams: grams,
 | 
				
			||||||
      productId: products[0].id,
 | 
					      productId: products[0].id,
 | 
				
			||||||
      mealId: meal!.id,
 | 
					      mealId: meal!.id,
 | 
				
			||||||
| 
						 | 
					@ -117,9 +109,7 @@ class _AddEntryScreen extends BasedState<AddEntryScreen> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (res is String) {
 | 
					    if (res is String) {
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        var productMap = await apiClient.getProductByBarcode(res);
 | 
					        var product = await client.product.getByBarcode(res);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        var product = Product.fromJson(productMap);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        setState(() {
 | 
					        setState(() {
 | 
				
			||||||
          products = [product];
 | 
					          products = [product];
 | 
				
			||||||
| 
						 | 
					@ -185,8 +175,7 @@ class _AddEntryScreen extends BasedState<AddEntryScreen> {
 | 
				
			||||||
                    context,
 | 
					                    context,
 | 
				
			||||||
                    MaterialPageRoute(
 | 
					                    MaterialPageRoute(
 | 
				
			||||||
                      builder: (context) => AddProductScreen(
 | 
					                      builder: (context) => AddProductScreen(
 | 
				
			||||||
                        apiClient: apiClient,
 | 
					                        ctx: ctx,
 | 
				
			||||||
                        storage: storage,
 | 
					 | 
				
			||||||
                      ),
 | 
					                      ),
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
                  ).then((product) {
 | 
					                  ).then((product) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,11 +9,7 @@ import 'package:fooder/components/floating_action_button.dart';
 | 
				
			||||||
class AddMealScreen extends BasedScreen {
 | 
					class AddMealScreen extends BasedScreen {
 | 
				
			||||||
  final Diary diary;
 | 
					  final Diary diary;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const AddMealScreen(
 | 
					  const AddMealScreen({super.key, required super.ctx, required this.diary});
 | 
				
			||||||
      {super.key,
 | 
					 | 
				
			||||||
      required super.apiClient,
 | 
					 | 
				
			||||||
      required super.storage,
 | 
					 | 
				
			||||||
      required this.diary});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  State<AddMealScreen> createState() => _AddMealScreen();
 | 
					  State<AddMealScreen> createState() => _AddMealScreen();
 | 
				
			||||||
| 
						 | 
					@ -28,12 +24,10 @@ class _AddMealScreen extends BasedState<AddMealScreen> {
 | 
				
			||||||
  Diary get diary => widget.diary;
 | 
					  Diary get diary => widget.diary;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<void> _getPresets() async {
 | 
					  Future<void> _getPresets() async {
 | 
				
			||||||
    var presetsMap = await apiClient.getPresets(presetNameController.text);
 | 
					    var presets = await client.preset.list(presetNameController.text);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    setState(() {
 | 
					    setState(() {
 | 
				
			||||||
      presets = (presetsMap['presets'] as List<dynamic>)
 | 
					      this.presets = presets;
 | 
				
			||||||
          .map((e) => Preset.fromJson(e as Map<String, dynamic>))
 | 
					 | 
				
			||||||
          .toList();
 | 
					 | 
				
			||||||
      selectedPreset = null;
 | 
					      selectedPreset = null;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -59,22 +53,22 @@ class _AddMealScreen extends BasedState<AddMealScreen> {
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<void> _addMeal() async {
 | 
					  Future<void> addMeal() async {
 | 
				
			||||||
    await apiClient.addMeal(
 | 
					    await client.meal.create(
 | 
				
			||||||
      name: nameController.text,
 | 
					      name: nameController.text,
 | 
				
			||||||
      diaryId: diary.id,
 | 
					      diaryId: diary.id,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    popMeDaddy();
 | 
					    popMeDaddy();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<void> _deletePreset(Preset preset) async {
 | 
					  Future<void> deletePreset(Preset preset) async {
 | 
				
			||||||
    apiClient.deletePreset(preset.id);
 | 
					    client.preset.delete(preset.id);
 | 
				
			||||||
    setState(() {
 | 
					    setState(() {
 | 
				
			||||||
      presets.remove(preset);
 | 
					      presets.remove(preset);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<void> deletePreset(context, Preset preset) async {
 | 
					  Future<void> deletePresetPopup(context, Preset preset) async {
 | 
				
			||||||
    showDialog(
 | 
					    showDialog(
 | 
				
			||||||
      context: context,
 | 
					      context: context,
 | 
				
			||||||
      builder: (context) {
 | 
					      builder: (context) {
 | 
				
			||||||
| 
						 | 
					@ -90,7 +84,7 @@ class _AddMealScreen extends BasedState<AddMealScreen> {
 | 
				
			||||||
            IconButton(
 | 
					            IconButton(
 | 
				
			||||||
              icon: const Icon(Icons.delete),
 | 
					              icon: const Icon(Icons.delete),
 | 
				
			||||||
              onPressed: () {
 | 
					              onPressed: () {
 | 
				
			||||||
                _deletePreset(preset);
 | 
					                deletePreset(preset);
 | 
				
			||||||
                Navigator.pop(context);
 | 
					                Navigator.pop(context);
 | 
				
			||||||
              },
 | 
					              },
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
| 
						 | 
					@ -100,13 +94,13 @@ class _AddMealScreen extends BasedState<AddMealScreen> {
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<void> _addMealFromPreset() async {
 | 
					  Future<void> addMealFromPreset() async {
 | 
				
			||||||
    if (selectedPreset == null) {
 | 
					    if (selectedPreset == null) {
 | 
				
			||||||
      _addMeal();
 | 
					      addMeal();
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await apiClient.addMealFromPreset(
 | 
					    await client.meal.createFromPreset(
 | 
				
			||||||
      name: nameChanged ? nameController.text : selectedPreset!.name,
 | 
					      name: nameChanged ? nameController.text : selectedPreset!.name,
 | 
				
			||||||
      diaryId: diary.id,
 | 
					      diaryId: diary.id,
 | 
				
			||||||
      presetId: selectedPreset!.id,
 | 
					      presetId: selectedPreset!.id,
 | 
				
			||||||
| 
						 | 
					@ -143,10 +137,10 @@ class _AddMealScreen extends BasedState<AddMealScreen> {
 | 
				
			||||||
                    presetNameController.text = preset.name;
 | 
					                    presetNameController.text = preset.name;
 | 
				
			||||||
                    selectedPreset = preset;
 | 
					                    selectedPreset = preset;
 | 
				
			||||||
                  });
 | 
					                  });
 | 
				
			||||||
                  _addMealFromPreset();
 | 
					                  addMealFromPreset();
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                onLongPress: () {
 | 
					                onLongPress: () {
 | 
				
			||||||
                  deletePreset(context, preset);
 | 
					                  deletePresetPopup(context, preset);
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                title: PresetWidget(
 | 
					                title: PresetWidget(
 | 
				
			||||||
                  preset: preset,
 | 
					                  preset: preset,
 | 
				
			||||||
| 
						 | 
					@ -156,7 +150,7 @@ class _AddMealScreen extends BasedState<AddMealScreen> {
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
      floatingActionButton: FActionButton(
 | 
					      floatingActionButton: FActionButton(
 | 
				
			||||||
        onPressed: _addMealFromPreset,
 | 
					        onPressed: addMealFromPreset,
 | 
				
			||||||
        icon: Icons.playlist_add_rounded,
 | 
					        icon: Icons.playlist_add_rounded,
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,8 +7,7 @@ import 'package:fooder/components/text.dart';
 | 
				
			||||||
import 'package:fooder/components/floating_action_button.dart';
 | 
					import 'package:fooder/components/floating_action_button.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AddProductScreen extends BasedScreen {
 | 
					class AddProductScreen extends BasedScreen {
 | 
				
			||||||
  const AddProductScreen(
 | 
					  const AddProductScreen({super.key, required super.ctx});
 | 
				
			||||||
      {super.key, required super.apiClient, required super.storage});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  State<AddProductScreen> createState() => _AddProductScreen();
 | 
					  State<AddProductScreen> createState() => _AddProductScreen();
 | 
				
			||||||
| 
						 | 
					@ -38,38 +37,25 @@ class _AddProductScreen extends BasedState<AddProductScreen> {
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<double?> _parseDouble(String text, String name,
 | 
					  Future<void> addProduct() async {
 | 
				
			||||||
      {bool silent = false}) async {
 | 
					    var carb = await parseDouble(carbController.text, "Carbs");
 | 
				
			||||||
    try {
 | 
					    var fat = await parseDouble(fatController.text, "Fat");
 | 
				
			||||||
      return double.parse(text.replaceAll(",", "."));
 | 
					    var protein = await parseDouble(proteinController.text, "Protein");
 | 
				
			||||||
    } 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");
 | 
					 | 
				
			||||||
    var fiber =
 | 
					    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) {
 | 
					    if (carb == null || fat == null || protein == null) {
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      var productJson = await apiClient.addProduct(
 | 
					      var product = await client.product.create(
 | 
				
			||||||
        carb: carb,
 | 
					        carb: carb,
 | 
				
			||||||
        fat: fat,
 | 
					        fat: fat,
 | 
				
			||||||
        protein: protein,
 | 
					        protein: protein,
 | 
				
			||||||
        fiber: fiber,
 | 
					        fiber: fiber,
 | 
				
			||||||
        name: nameController.text,
 | 
					        name: nameController.text,
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      var product = Product.fromJson(productJson);
 | 
					 | 
				
			||||||
      popMeDaddy(product);
 | 
					      popMeDaddy(product);
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
      showError(
 | 
					      showError(
 | 
				
			||||||
| 
						 | 
					@ -195,7 +181,7 @@ class _AddProductScreen extends BasedState<AddProductScreen> {
 | 
				
			||||||
            ])),
 | 
					            ])),
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
      floatingActionButton: FActionButton(
 | 
					      floatingActionButton: FActionButton(
 | 
				
			||||||
        onPressed: _addProduct,
 | 
					        onPressed: addProduct,
 | 
				
			||||||
        icon: Icons.save,
 | 
					        icon: Icons.save,
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,5 @@
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:fooder/context.dart';
 | 
				
			||||||
import 'package:fooder/client.dart';
 | 
					import 'package:fooder/client.dart';
 | 
				
			||||||
import 'package:fooder/storage.dart';
 | 
					import 'package:fooder/storage.dart';
 | 
				
			||||||
import 'package:fooder/components/app_bar.dart';
 | 
					import 'package:fooder/components/app_bar.dart';
 | 
				
			||||||
| 
						 | 
					@ -13,19 +14,18 @@ TextStyle logoStyle(context) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
abstract class BasedScreen extends StatefulWidget {
 | 
					abstract class BasedScreen extends StatefulWidget {
 | 
				
			||||||
  final ApiClient apiClient;
 | 
					  final Context ctx;
 | 
				
			||||||
  final Storage storage;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const BasedScreen(
 | 
					  const BasedScreen({super.key, required this.ctx});
 | 
				
			||||||
      {super.key, required this.apiClient, required this.storage});
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
abstract class BasedState<T extends BasedScreen> extends State<T> {
 | 
					abstract class BasedState<T extends BasedScreen> extends State<T> {
 | 
				
			||||||
  ApiClient get apiClient => widget.apiClient;
 | 
					  Context get ctx => widget.ctx;
 | 
				
			||||||
  Storage get storage => widget.storage;
 | 
					  Client get client => widget.ctx.client;
 | 
				
			||||||
 | 
					  Storage get storage => widget.ctx.storage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void logout() async {
 | 
					  void logout() async {
 | 
				
			||||||
    await apiClient.logout();
 | 
					    await client.api.logout();
 | 
				
			||||||
    backToLogin();
 | 
					    backToLogin();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -33,8 +33,7 @@ abstract class BasedState<T extends BasedScreen> extends State<T> {
 | 
				
			||||||
    Navigator.pushReplacement(
 | 
					    Navigator.pushReplacement(
 | 
				
			||||||
      context,
 | 
					      context,
 | 
				
			||||||
      MaterialPageRoute(
 | 
					      MaterialPageRoute(
 | 
				
			||||||
        builder: (context) =>
 | 
					        builder: (context) => LoginScreen(ctx: ctx),
 | 
				
			||||||
            LoginScreen(apiClient: apiClient, storage: storage),
 | 
					 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -43,8 +42,7 @@ abstract class BasedState<T extends BasedScreen> extends State<T> {
 | 
				
			||||||
    Navigator.pushReplacement(
 | 
					    Navigator.pushReplacement(
 | 
				
			||||||
      context,
 | 
					      context,
 | 
				
			||||||
      MaterialPageRoute(
 | 
					      MaterialPageRoute(
 | 
				
			||||||
        builder: (context) =>
 | 
					        builder: (context) => MainScreen(ctx: ctx),
 | 
				
			||||||
            MainScreen(apiClient: apiClient, storage: storage),
 | 
					 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -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) {
 | 
					  void showError(String message) {
 | 
				
			||||||
    ScaffoldMessenger.of(context).showSnackBar(
 | 
					    ScaffoldMessenger.of(context).showSnackBar(
 | 
				
			||||||
      SnackBar(
 | 
					      SnackBar(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,11 +12,7 @@ import 'package:simple_barcode_scanner/simple_barcode_scanner.dart';
 | 
				
			||||||
class EditEntryScreen extends BasedScreen {
 | 
					class EditEntryScreen extends BasedScreen {
 | 
				
			||||||
  final Entry entry;
 | 
					  final Entry entry;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const EditEntryScreen(
 | 
					  const EditEntryScreen({super.key, required super.ctx, required this.entry});
 | 
				
			||||||
      {super.key,
 | 
					 | 
				
			||||||
      required super.apiClient,
 | 
					 | 
				
			||||||
      required super.storage,
 | 
					 | 
				
			||||||
      required this.entry});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  State<EditEntryScreen> createState() => _EditEntryScreen();
 | 
					  State<EditEntryScreen> createState() => _EditEntryScreen();
 | 
				
			||||||
| 
						 | 
					@ -26,7 +22,7 @@ class _EditEntryScreen extends BasedState<EditEntryScreen> {
 | 
				
			||||||
  final gramsController = TextEditingController();
 | 
					  final gramsController = TextEditingController();
 | 
				
			||||||
  final productNameController = TextEditingController();
 | 
					  final productNameController = TextEditingController();
 | 
				
			||||||
  List<Product> products = [];
 | 
					  List<Product> products = [];
 | 
				
			||||||
  Entry get entry => entry;
 | 
					  Entry get entry => widget.entry;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  void dispose() {
 | 
					  void dispose() {
 | 
				
			||||||
| 
						 | 
					@ -49,36 +45,25 @@ class _EditEntryScreen extends BasedState<EditEntryScreen> {
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<void> _getProducts() async {
 | 
					  Future<void> getProducts() async {
 | 
				
			||||||
    var productsMap = await apiClient.getProducts(productNameController.text);
 | 
					    var products = await client.product.list(productNameController.text);
 | 
				
			||||||
    setState(() {
 | 
					    setState(() {
 | 
				
			||||||
      products = (productsMap['products'] as List<dynamic>)
 | 
					      this.products = products;
 | 
				
			||||||
          .map((e) => Product.fromJson(e as Map<String, dynamic>))
 | 
					 | 
				
			||||||
          .toList();
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<double?> _parseDouble(String text, String name) async {
 | 
					  Future<void> saveEntry() async {
 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      return double.parse(text.replaceAll(",", "."));
 | 
					 | 
				
			||||||
    } catch (e) {
 | 
					 | 
				
			||||||
      showError("$name must be a number");
 | 
					 | 
				
			||||||
      return null;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Future<void> _saveEntry() async {
 | 
					 | 
				
			||||||
    if (products.length != 1) {
 | 
					    if (products.length != 1) {
 | 
				
			||||||
      showError("Pick product first");
 | 
					      showError("Pick product first");
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var grams = await _parseDouble(gramsController.text, "Grams");
 | 
					    var grams = await parseDouble(gramsController.text, "Grams");
 | 
				
			||||||
    if (grams == null) {
 | 
					    if (grams == null) {
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await apiClient.updateEntry(
 | 
					    await client.entry.update(
 | 
				
			||||||
      entry.id,
 | 
					      entry.id,
 | 
				
			||||||
      grams: grams,
 | 
					      grams: grams,
 | 
				
			||||||
      productId: products[0].id,
 | 
					      productId: products[0].id,
 | 
				
			||||||
| 
						 | 
					@ -87,12 +72,12 @@ class _EditEntryScreen extends BasedState<EditEntryScreen> {
 | 
				
			||||||
    popMeDaddy();
 | 
					    popMeDaddy();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<void> _deleteEntry() async {
 | 
					  Future<void> deleteEntry() async {
 | 
				
			||||||
    await apiClient.deleteEntry(widget.entry.id);
 | 
					    await client.entry.delete(widget.entry.id);
 | 
				
			||||||
    popMeDaddy();
 | 
					    popMeDaddy();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<void> _findProductByBarCode() async {
 | 
					  Future<void> findProductByBarCode() async {
 | 
				
			||||||
    var res = await Navigator.push(
 | 
					    var res = await Navigator.push(
 | 
				
			||||||
      context,
 | 
					      context,
 | 
				
			||||||
      MaterialPageRoute(
 | 
					      MaterialPageRoute(
 | 
				
			||||||
| 
						 | 
					@ -102,9 +87,7 @@ class _EditEntryScreen extends BasedState<EditEntryScreen> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (res is String) {
 | 
					    if (res is String) {
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        var productMap = await apiClient.getProductByBarcode(res);
 | 
					        var product = await client.product.getByBarcode(res);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        var product = Product.fromJson(productMap);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        setState(() {
 | 
					        setState(() {
 | 
				
			||||||
          products = [product];
 | 
					          products = [product];
 | 
				
			||||||
| 
						 | 
					@ -128,7 +111,7 @@ class _EditEntryScreen extends BasedState<EditEntryScreen> {
 | 
				
			||||||
              FTextInput(
 | 
					              FTextInput(
 | 
				
			||||||
                labelText: 'Product name',
 | 
					                labelText: 'Product name',
 | 
				
			||||||
                controller: productNameController,
 | 
					                controller: productNameController,
 | 
				
			||||||
                onChanged: (_) => _getProducts(),
 | 
					                onChanged: (_) => getProducts(),
 | 
				
			||||||
                autofocus: true,
 | 
					                autofocus: true,
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
              FTextInput(
 | 
					              FTextInput(
 | 
				
			||||||
| 
						 | 
					@ -147,8 +130,7 @@ class _EditEntryScreen extends BasedState<EditEntryScreen> {
 | 
				
			||||||
                    context,
 | 
					                    context,
 | 
				
			||||||
                    MaterialPageRoute(
 | 
					                    MaterialPageRoute(
 | 
				
			||||||
                      builder: (context) => AddProductScreen(
 | 
					                      builder: (context) => AddProductScreen(
 | 
				
			||||||
                        apiClient: apiClient,
 | 
					                        ctx: ctx,
 | 
				
			||||||
                        storage: storage,
 | 
					 | 
				
			||||||
                      ),
 | 
					                      ),
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
                  ).then((product) {
 | 
					                  ).then((product) {
 | 
				
			||||||
| 
						 | 
					@ -181,18 +163,18 @@ class _EditEntryScreen extends BasedState<EditEntryScreen> {
 | 
				
			||||||
        mainAxisAlignment: MainAxisAlignment.end,
 | 
					        mainAxisAlignment: MainAxisAlignment.end,
 | 
				
			||||||
        children: <Widget>[
 | 
					        children: <Widget>[
 | 
				
			||||||
          FActionButton(
 | 
					          FActionButton(
 | 
				
			||||||
            onPressed: _findProductByBarCode,
 | 
					            onPressed: findProductByBarCode,
 | 
				
			||||||
            icon: Icons.photo_camera,
 | 
					            icon: Icons.photo_camera,
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
          const SizedBox(width: 10),
 | 
					          const SizedBox(width: 10),
 | 
				
			||||||
          FActionButton(
 | 
					          FActionButton(
 | 
				
			||||||
            onPressed: _deleteEntry,
 | 
					            onPressed: deleteEntry,
 | 
				
			||||||
            tag: "fap1",
 | 
					            tag: "fap1",
 | 
				
			||||||
            icon: Icons.delete,
 | 
					            icon: Icons.delete,
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
          const SizedBox(width: 10),
 | 
					          const SizedBox(width: 10),
 | 
				
			||||||
          FActionButton(
 | 
					          FActionButton(
 | 
				
			||||||
            onPressed: _saveEntry,
 | 
					            onPressed: saveEntry,
 | 
				
			||||||
            tag: "fap2",
 | 
					            tag: "fap2",
 | 
				
			||||||
            icon: Icons.save,
 | 
					            icon: Icons.save,
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,8 +7,7 @@ import 'package:fooder/components/text.dart';
 | 
				
			||||||
import 'package:fooder/components/button.dart';
 | 
					import 'package:fooder/components/button.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class LoginScreen extends BasedScreen {
 | 
					class LoginScreen extends BasedScreen {
 | 
				
			||||||
  const LoginScreen(
 | 
					  const LoginScreen({super.key, required super.ctx});
 | 
				
			||||||
      {super.key, required super.apiClient, required super.storage});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  State<LoginScreen> createState() => _LoginScreen();
 | 
					  State<LoginScreen> createState() => _LoginScreen();
 | 
				
			||||||
| 
						 | 
					@ -29,16 +28,15 @@ class _LoginScreen extends BasedState<LoginScreen> {
 | 
				
			||||||
    Navigator.pushReplacement(
 | 
					    Navigator.pushReplacement(
 | 
				
			||||||
      context,
 | 
					      context,
 | 
				
			||||||
      MaterialPageRoute(
 | 
					      MaterialPageRoute(
 | 
				
			||||||
        builder: (context) =>
 | 
					        builder: (context) => MainScreen(ctx: ctx),
 | 
				
			||||||
            MainScreen(apiClient: apiClient, storage: storage),
 | 
					 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // login client when button pressed
 | 
					  // login client when button pressed
 | 
				
			||||||
  Future<void> _login() async {
 | 
					  Future<void> login() async {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      await apiClient.login(
 | 
					      await client.api.login(
 | 
				
			||||||
        usernameController.text,
 | 
					        usernameController.text,
 | 
				
			||||||
        passwordController.text,
 | 
					        passwordController.text,
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
| 
						 | 
					@ -60,18 +58,18 @@ class _LoginScreen extends BasedState<LoginScreen> {
 | 
				
			||||||
        AutofillHints.password,
 | 
					        AutofillHints.password,
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    _asyncInitState().then((value) => null);
 | 
					    asyncInitState().then((value) => null);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<void> _asyncInitState() async {
 | 
					  Future<void> asyncInitState() async {
 | 
				
			||||||
    await apiClient.loadToken();
 | 
					    await client.api.loadToken();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (apiClient.refreshToken == null) {
 | 
					    if (client.api.refreshToken == null) {
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      await apiClient.refresh();
 | 
					      await client.api.refresh();
 | 
				
			||||||
      showText("Welcome back!");
 | 
					      showText("Welcome back!");
 | 
				
			||||||
      popMeDaddy();
 | 
					      popMeDaddy();
 | 
				
			||||||
    } on Exception catch (_) {
 | 
					    } on Exception catch (_) {
 | 
				
			||||||
| 
						 | 
					@ -107,13 +105,13 @@ class _LoginScreen extends BasedState<LoginScreen> {
 | 
				
			||||||
                FTextInput(
 | 
					                FTextInput(
 | 
				
			||||||
                  labelText: 'Password',
 | 
					                  labelText: 'Password',
 | 
				
			||||||
                  controller: passwordController,
 | 
					                  controller: passwordController,
 | 
				
			||||||
                  onFieldSubmitted: (_) => _login(),
 | 
					                  onFieldSubmitted: (_) => login(),
 | 
				
			||||||
                  autofillHints: const [AutofillHints.password],
 | 
					                  autofillHints: const [AutofillHints.password],
 | 
				
			||||||
                  obscureText: true,
 | 
					                  obscureText: true,
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
                FButton(
 | 
					                FButton(
 | 
				
			||||||
                  labelText: 'Sign In',
 | 
					                  labelText: 'Sign In',
 | 
				
			||||||
                  onPressed: _login,
 | 
					                  onPressed: login,
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
                Padding(
 | 
					                Padding(
 | 
				
			||||||
                  padding: const EdgeInsets.symmetric(vertical: 8),
 | 
					                  padding: const EdgeInsets.symmetric(vertical: 8),
 | 
				
			||||||
| 
						 | 
					@ -123,8 +121,7 @@ class _LoginScreen extends BasedState<LoginScreen> {
 | 
				
			||||||
                        context,
 | 
					                        context,
 | 
				
			||||||
                        MaterialPageRoute(
 | 
					                        MaterialPageRoute(
 | 
				
			||||||
                          builder: (context) => RegisterScreen(
 | 
					                          builder: (context) => RegisterScreen(
 | 
				
			||||||
                            apiClient: apiClient,
 | 
					                            ctx: ctx,
 | 
				
			||||||
                            storage: storage,
 | 
					 | 
				
			||||||
                          ),
 | 
					                          ),
 | 
				
			||||||
                        ),
 | 
					                        ),
 | 
				
			||||||
                      );
 | 
					                      );
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,8 +10,7 @@ import 'package:fooder/components/date_picker.dart';
 | 
				
			||||||
import 'package:fooder/components/floating_action_button.dart';
 | 
					import 'package:fooder/components/floating_action_button.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MainScreen extends BasedScreen {
 | 
					class MainScreen extends BasedScreen {
 | 
				
			||||||
  const MainScreen(
 | 
					  const MainScreen({super.key, required super.ctx});
 | 
				
			||||||
      {super.key, required super.apiClient, required super.storage});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  State<MainScreen> createState() => _MainScreen();
 | 
					  State<MainScreen> createState() => _MainScreen();
 | 
				
			||||||
| 
						 | 
					@ -28,10 +27,10 @@ class _MainScreen extends BasedState<MainScreen> {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<void> _asyncInitState() async {
 | 
					  Future<void> _asyncInitState() async {
 | 
				
			||||||
    var diaryMap = await apiClient.getDiary(date: date);
 | 
					    var diary = await client.diary.get(date: date);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    setState(() {
 | 
					    setState(() {
 | 
				
			||||||
      diary = Diary.fromJson(diaryMap);
 | 
					      this.diary = diary;
 | 
				
			||||||
      date = date;
 | 
					      date = date;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -52,8 +51,7 @@ class _MainScreen extends BasedState<MainScreen> {
 | 
				
			||||||
    await Navigator.push(
 | 
					    await Navigator.push(
 | 
				
			||||||
      context,
 | 
					      context,
 | 
				
			||||||
      MaterialPageRoute(
 | 
					      MaterialPageRoute(
 | 
				
			||||||
        builder: (context) => AddEntryScreen(
 | 
					        builder: (context) => AddEntryScreen(ctx: ctx, diary: diary!),
 | 
				
			||||||
            apiClient: apiClient, diary: diary!, storage: storage),
 | 
					 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
    ).then((_) => _asyncInitState());
 | 
					    ).then((_) => _asyncInitState());
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -67,9 +65,8 @@ class _MainScreen extends BasedState<MainScreen> {
 | 
				
			||||||
      context,
 | 
					      context,
 | 
				
			||||||
      MaterialPageRoute(
 | 
					      MaterialPageRoute(
 | 
				
			||||||
        builder: (context) => AddMealScreen(
 | 
					        builder: (context) => AddMealScreen(
 | 
				
			||||||
          apiClient: apiClient,
 | 
					          ctx: ctx,
 | 
				
			||||||
          diary: diary!,
 | 
					          diary: diary!,
 | 
				
			||||||
          storage: storage,
 | 
					 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
    ).then((_) => _asyncInitState());
 | 
					    ).then((_) => _asyncInitState());
 | 
				
			||||||
| 
						 | 
					@ -91,13 +88,11 @@ class _MainScreen extends BasedState<MainScreen> {
 | 
				
			||||||
            [
 | 
					            [
 | 
				
			||||||
              SummaryWidget(
 | 
					              SummaryWidget(
 | 
				
			||||||
                diary: diary!,
 | 
					                diary: diary!,
 | 
				
			||||||
                apiClient: apiClient,
 | 
					 | 
				
			||||||
                refreshParent: _asyncInitState,
 | 
					                refreshParent: _asyncInitState,
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
              for (var (i, meal) in diary!.meals.indexed)
 | 
					              for (var (i, meal) in diary!.meals.indexed)
 | 
				
			||||||
                MealWidget(
 | 
					                MealWidget(
 | 
				
			||||||
                  apiClient: apiClient,
 | 
					                  ctx: ctx,
 | 
				
			||||||
                  storage: storage,
 | 
					 | 
				
			||||||
                  meal: meal,
 | 
					                  meal: meal,
 | 
				
			||||||
                  refreshParent: _asyncInitState,
 | 
					                  refreshParent: _asyncInitState,
 | 
				
			||||||
                  initiallyExpanded: i == 0,
 | 
					                  initiallyExpanded: i == 0,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,8 +5,7 @@ import 'package:fooder/components/text.dart';
 | 
				
			||||||
import 'package:fooder/components/button.dart';
 | 
					import 'package:fooder/components/button.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class RegisterScreen extends BasedScreen {
 | 
					class RegisterScreen extends BasedScreen {
 | 
				
			||||||
  const RegisterScreen(
 | 
					  const RegisterScreen({super.key, required super.ctx});
 | 
				
			||||||
      {super.key, required super.apiClient, required super.storage});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  State<RegisterScreen> createState() => _RegisterScreen();
 | 
					  State<RegisterScreen> createState() => _RegisterScreen();
 | 
				
			||||||
| 
						 | 
					@ -50,7 +49,7 @@ class _RegisterScreen extends BasedState<RegisterScreen> {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      await apiClient.register(
 | 
					      await client.api.register(
 | 
				
			||||||
        usernameController.text,
 | 
					        usernameController.text,
 | 
				
			||||||
        passwordController.text,
 | 
					        passwordController.text,
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,8 +3,7 @@ import 'package:fooder/models/meal.dart';
 | 
				
			||||||
import 'package:fooder/widgets/entry.dart';
 | 
					import 'package:fooder/widgets/entry.dart';
 | 
				
			||||||
import 'package:fooder/widgets/macro.dart';
 | 
					import 'package:fooder/widgets/macro.dart';
 | 
				
			||||||
import 'package:fooder/screens/edit_entry.dart';
 | 
					import 'package:fooder/screens/edit_entry.dart';
 | 
				
			||||||
import 'package:fooder/client.dart';
 | 
					import 'package:fooder/context.dart';
 | 
				
			||||||
import 'package:fooder/storage.dart';
 | 
					 | 
				
			||||||
import 'dart:core';
 | 
					import 'dart:core';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MealHeader extends StatelessWidget {
 | 
					class MealHeader extends StatelessWidget {
 | 
				
			||||||
| 
						 | 
					@ -38,8 +37,7 @@ class MealWidget extends StatelessWidget {
 | 
				
			||||||
  static const maxWidth = 920.0;
 | 
					  static const maxWidth = 920.0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  final Meal meal;
 | 
					  final Meal meal;
 | 
				
			||||||
  final ApiClient apiClient;
 | 
					  final Context ctx;
 | 
				
			||||||
  final Storage storage;
 | 
					 | 
				
			||||||
  final Function() refreshParent;
 | 
					  final Function() refreshParent;
 | 
				
			||||||
  final Function(String) showText;
 | 
					  final Function(String) showText;
 | 
				
			||||||
  final bool initiallyExpanded;
 | 
					  final bool initiallyExpanded;
 | 
				
			||||||
| 
						 | 
					@ -47,11 +45,10 @@ class MealWidget extends StatelessWidget {
 | 
				
			||||||
  const MealWidget({
 | 
					  const MealWidget({
 | 
				
			||||||
    super.key,
 | 
					    super.key,
 | 
				
			||||||
    required this.meal,
 | 
					    required this.meal,
 | 
				
			||||||
    required this.apiClient,
 | 
					    required this.ctx,
 | 
				
			||||||
    required this.refreshParent,
 | 
					    required this.refreshParent,
 | 
				
			||||||
    required this.initiallyExpanded,
 | 
					    required this.initiallyExpanded,
 | 
				
			||||||
    required this.showText,
 | 
					    required this.showText,
 | 
				
			||||||
    required this.storage,
 | 
					 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<void> saveMeal(context) async {
 | 
					  Future<void> saveMeal(context) async {
 | 
				
			||||||
| 
						 | 
					@ -77,7 +74,7 @@ class MealWidget extends StatelessWidget {
 | 
				
			||||||
            IconButton(
 | 
					            IconButton(
 | 
				
			||||||
              icon: const Icon(Icons.save),
 | 
					              icon: const Icon(Icons.save),
 | 
				
			||||||
              onPressed: () {
 | 
					              onPressed: () {
 | 
				
			||||||
                apiClient.saveMeal(meal, textFieldController.text);
 | 
					                ctx.client.meal.update(meal.id, textFieldController.text);
 | 
				
			||||||
                Navigator.pop(context);
 | 
					                Navigator.pop(context);
 | 
				
			||||||
                showText("Meal saved");
 | 
					                showText("Meal saved");
 | 
				
			||||||
              },
 | 
					              },
 | 
				
			||||||
| 
						 | 
					@ -88,11 +85,11 @@ class MealWidget extends StatelessWidget {
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<void> _deleteMeal(Meal meal) async {
 | 
					  Future<void> deleteMeal(Meal meal) async {
 | 
				
			||||||
    await apiClient.deleteMeal(meal.id);
 | 
					    await ctx.client.meal.delete(meal.id);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<void> deleteMeal(context) async {
 | 
					  Future<void> deleteMealPopup(context) async {
 | 
				
			||||||
    showDialog(
 | 
					    showDialog(
 | 
				
			||||||
      context: context,
 | 
					      context: context,
 | 
				
			||||||
      barrierDismissible: false,
 | 
					      barrierDismissible: false,
 | 
				
			||||||
| 
						 | 
					@ -109,7 +106,7 @@ class MealWidget extends StatelessWidget {
 | 
				
			||||||
            IconButton(
 | 
					            IconButton(
 | 
				
			||||||
              icon: const Icon(Icons.delete),
 | 
					              icon: const Icon(Icons.delete),
 | 
				
			||||||
              onPressed: () {
 | 
					              onPressed: () {
 | 
				
			||||||
                _deleteMeal(meal).then((_) => refreshParent());
 | 
					                deleteMeal(meal).then((_) => refreshParent());
 | 
				
			||||||
                Navigator.pop(context);
 | 
					                Navigator.pop(context);
 | 
				
			||||||
                showText("Meal deleted");
 | 
					                showText("Meal deleted");
 | 
				
			||||||
              },
 | 
					              },
 | 
				
			||||||
| 
						 | 
					@ -125,9 +122,8 @@ class MealWidget extends StatelessWidget {
 | 
				
			||||||
      context,
 | 
					      context,
 | 
				
			||||||
      MaterialPageRoute(
 | 
					      MaterialPageRoute(
 | 
				
			||||||
        builder: (context) => EditEntryScreen(
 | 
					        builder: (context) => EditEntryScreen(
 | 
				
			||||||
          apiClient: apiClient,
 | 
					          ctx: ctx,
 | 
				
			||||||
          entry: entry,
 | 
					          entry: entry,
 | 
				
			||||||
          storage: storage,
 | 
					 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
    ).then((_) => refreshParent());
 | 
					    ).then((_) => refreshParent());
 | 
				
			||||||
| 
						 | 
					@ -195,7 +191,7 @@ class MealWidget extends StatelessWidget {
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
                    IconButton(
 | 
					                    IconButton(
 | 
				
			||||||
                      icon: const Icon(Icons.delete),
 | 
					                      icon: const Icon(Icons.delete),
 | 
				
			||||||
                      onPressed: () => deleteMeal(context),
 | 
					                      onPressed: () => deleteMealPopup(context),
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
                  ],
 | 
					                  ],
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,6 @@
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:fooder/models/diary.dart';
 | 
					import 'package:fooder/models/diary.dart';
 | 
				
			||||||
import 'package:fooder/widgets/macro.dart';
 | 
					import 'package:fooder/widgets/macro.dart';
 | 
				
			||||||
import 'package:fooder/client.dart';
 | 
					 | 
				
			||||||
import 'dart:core';
 | 
					import 'dart:core';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SummaryHeader extends StatelessWidget {
 | 
					class SummaryHeader extends StatelessWidget {
 | 
				
			||||||
| 
						 | 
					@ -33,14 +32,10 @@ class SummaryWidget extends StatelessWidget {
 | 
				
			||||||
  static const maxWidth = 920.0;
 | 
					  static const maxWidth = 920.0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  final Diary diary;
 | 
					  final Diary diary;
 | 
				
			||||||
  final ApiClient apiClient;
 | 
					 | 
				
			||||||
  final Function() refreshParent;
 | 
					  final Function() refreshParent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const SummaryWidget(
 | 
					  const SummaryWidget(
 | 
				
			||||||
      {super.key,
 | 
					      {super.key, required this.diary, required this.refreshParent});
 | 
				
			||||||
      required this.diary,
 | 
					 | 
				
			||||||
      required this.apiClient,
 | 
					 | 
				
			||||||
      required this.refreshParent});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue