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