From 31fe40c195467394b5d7edadf6dec7ad067e21f6 Mon Sep 17 00:00:00 2001 From: Tom Date: Sun, 30 Jul 2023 11:33:20 +0200 Subject: [PATCH 1/7] Fix #35: Use simpler fractions Fix #37: Use better calculation --- .../widgets/recipe_detail_tabbar_widget.dart | 23 +++++++++++++++++-- .../recipe_shopping_list_stateful_widget.dart | 21 +++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/lib/components/widgets/recipe_detail_tabbar_widget.dart b/lib/components/widgets/recipe_detail_tabbar_widget.dart index 9dd9dad..f83ffed 100644 --- a/lib/components/widgets/recipe_detail_tabbar_widget.dart +++ b/lib/components/widgets/recipe_detail_tabbar_widget.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:untare/cubits/settings_cubit.dart'; @@ -262,11 +264,28 @@ class RecipeDetailTabBarWidgetState extends State { SettingsCubit settingsCubit = context.read(); bool? useFractions = (settingsCubit.state.userServerSetting!.useFractions == true); - double rawAmount = (ingredient.amount * (((newServing/initServing))*100).ceil()/100); + double rawAmount = ingredient.amount * newServing / initServing; String amount = (ingredient.amount > 0) ? ('${rawAmount.toFormattedString()} ') : ''; if (amount != '' && useFractions == true && (rawAmount % 1) != 0) { - amount = '${rawAmount.toMixedFraction()} '; + // If we have a complex decimal we build a "simple" fraction. Otherwise we do the normal one + if ((((rawAmount - rawAmount.toInt()) * 100) % 5) != 0) { + // Use this crap because we can't change precision programmatically + if (rawAmount.toInt() < 1) { + amount = '${MixedFraction.fromDouble(rawAmount, precision: 1.0e-1).reduce()} '; + } else if (rawAmount.toInt() < 10) { + amount = '${MixedFraction.fromDouble(rawAmount, precision: 1.0e-2).reduce()} '; + } else if (rawAmount.toInt() < 100) { + amount = '${MixedFraction.fromDouble(rawAmount, precision: 1.0e-3).reduce()} '; + } else if(rawAmount.toInt() < 1000) { + amount = '${MixedFraction.fromDouble(rawAmount, precision: 1.0e-4).reduce()} '; + } else { + amount = '${MixedFraction.fromDouble(rawAmount, precision: 1.0e-5).reduce()} '; + } + } else { + amount = '${MixedFraction.fromDouble(rawAmount)} '; + } } + String unit = (ingredient.amount > 0 && ingredient.unit != null) ? ('${ingredient.unit!.getUnitName(rawAmount)} ') : ''; String food = (ingredient.food != null) ? ('${ingredient.food!.getFoodName(rawAmount)} ') : ''; String note = (ingredient.note != null && ingredient.note != '') ? ('(${ingredient.note!})') : ''; diff --git a/lib/components/widgets/recipe_shopping_list_stateful_widget.dart b/lib/components/widgets/recipe_shopping_list_stateful_widget.dart index 7519d0c..80809cd 100644 --- a/lib/components/widgets/recipe_shopping_list_stateful_widget.dart +++ b/lib/components/widgets/recipe_shopping_list_stateful_widget.dart @@ -223,11 +223,28 @@ class RecipeShoppingListWidgetState extends State { SettingsCubit settingsCubit = context.read(); bool? useFractions = (settingsCubit.state.userServerSetting!.useFractions == true); - double rawAmount = (ingredient.amount * (((newServing/initServing))*100).ceil()/100); + double rawAmount = ingredient.amount * newServing / initServing; String amount = (ingredient.amount > 0) ? ('${rawAmount.toFormattedString()} ') : ''; if (amount != '' && useFractions == true && (rawAmount % 1) != 0) { - amount = '${rawAmount.toMixedFraction()} '; + // If we have a complex decimal we build a "simple" fraction. Otherwise we do the normal one + if ((((rawAmount - rawAmount.toInt()) * 100) % 5) != 0) { + // Use this crap because we can't change precision programmatically + if (rawAmount.toInt() < 1) { + amount = '${MixedFraction.fromDouble(rawAmount, precision: 1.0e-1).reduce()} '; + } else if (rawAmount.toInt() < 10) { + amount = '${MixedFraction.fromDouble(rawAmount, precision: 1.0e-2).reduce()} '; + } else if (rawAmount.toInt() < 100) { + amount = '${MixedFraction.fromDouble(rawAmount, precision: 1.0e-3).reduce()} '; + } else if(rawAmount.toInt() < 1000) { + amount = '${MixedFraction.fromDouble(rawAmount, precision: 1.0e-4).reduce()} '; + } else { + amount = '${MixedFraction.fromDouble(rawAmount, precision: 1.0e-5).reduce()} '; + } + } else { + amount = '${MixedFraction.fromDouble(rawAmount)} '; + } } + String unit = (ingredient.unit != null && ingredient.amount > 0) ? ('${ingredient.unit!.getUnitName(rawAmount)} ') : ''; String food = (ingredient.food != null) ? ('${ingredient.food!.getFoodName(rawAmount)} ') : ''; bool? checkBoxValue = !(ingredient.food != null && ingredient.food!.onHand!); From 211ca10b00e153ef9110c9e111421ed90bbb77cd Mon Sep 17 00:00:00 2001 From: Tom Date: Sun, 30 Jul 2023 12:19:40 +0200 Subject: [PATCH 2/7] #36: Added some underlaying support but implementation is still open --- .../widgets/recipe_detail_tabbar_widget.dart | 28 +++++++++---------- lib/models/recipe.dart | 6 ++-- lib/models/step.dart | 19 +++++++++++-- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/lib/components/widgets/recipe_detail_tabbar_widget.dart b/lib/components/widgets/recipe_detail_tabbar_widget.dart index f83ffed..6160c1a 100644 --- a/lib/components/widgets/recipe_detail_tabbar_widget.dart +++ b/lib/components/widgets/recipe_detail_tabbar_widget.dart @@ -1,5 +1,3 @@ -import 'dart:math'; - import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:untare/cubits/settings_cubit.dart'; @@ -50,7 +48,7 @@ class RecipeDetailTabBarWidgetState extends State { return TabBarView( children: [ ingredientTabView(), - directionsTabView() + directionsTabView(widget.recipe) ], ); } @@ -116,24 +114,24 @@ class RecipeDetailTabBarWidgetState extends State { } } - Widget directionsTabView() { + Widget directionsTabView(Recipe recipe) { List directionsSteps = []; - if (widget.recipe.steps.isNotEmpty) { - if (widget.recipe.steps.length > 1) { - for (int i = 0; i < widget.recipe.steps.length; i++) { + if (recipe.steps.isNotEmpty) { + if (recipe.steps.length > 1) { + for (int i = 0; i < recipe.steps.length; i++) { List stepList = []; - stepList.addAll(widget.recipe.steps[i].ingredients.map((item) => ingredientComponent(item, servings, newServings, true, context)).toList()); + stepList.addAll(recipe.steps[i].ingredients.map((item) => ingredientComponent(item, servings, newServings, true, context)).toList()); - stepList.add(Padding(padding: const EdgeInsets.fromLTRB(20, 12, 15, 10), child: Text(widget.recipe.steps[i].instruction ?? '', style: const TextStyle(fontSize: 15)))); + stepList.add(Padding(padding: const EdgeInsets.fromLTRB(20, 12, 15, 10), child: Text(recipe.steps[i].instruction ?? '', style: const TextStyle(fontSize: 15)))); - directionsSteps.add(directionStepLayout(context, Column(crossAxisAlignment: CrossAxisAlignment.start, children: stepList), i+1, widget.recipe.steps[i].time, widget.recipe.steps[i].name)); + directionsSteps.add(directionStepLayout(context, Column(crossAxisAlignment: CrossAxisAlignment.start, children: stepList), i+1, recipe.steps[i].time, recipe.steps[i].name)); } - } else if (widget.recipe.steps.length == 1) { - List splitDirectionsStrings = (widget.recipe.steps.first.instruction != null && widget.recipe.steps.first.instruction != '') - ? widget.recipe.steps.first.instruction!.split("\n\n") + } else if (recipe.steps.length == 1) { + List splitDirectionsStrings = (recipe.steps.first.instruction != null && recipe.steps.first.instruction != '') + ? recipe.steps.first.instruction!.split("\n\n") : []; if (splitDirectionsStrings.length <= 2) { @@ -153,8 +151,8 @@ class RecipeDetailTabBarWidgetState extends State { context, Padding(padding: const EdgeInsets.fromLTRB(20, 12, 15, 10), child: Text(splitInstruction, style: const TextStyle(fontSize: 15))), i+1, - widget.recipe.steps.first.time, - widget.recipe.steps.first.name + recipe.steps.first.time, + recipe.steps.first.name ) ); } diff --git a/lib/models/recipe.dart b/lib/models/recipe.dart index daf63f4..fc8bb58 100644 --- a/lib/models/recipe.dart +++ b/lib/models/recipe.dart @@ -30,7 +30,7 @@ class Recipe { @HiveField(10) final String? updatedAt; @HiveField(11) - final bool internal; + final bool? internal; @HiveField(12) final int? servings; @HiveField(13) @@ -56,7 +56,7 @@ class Recipe { this.createdBy, this.createdAt, this.updatedAt, - required this.internal, + this.internal, this.servings, this.servingsText, this.rating, @@ -158,7 +158,7 @@ class Recipe { createdBy: json['created_by'] as int?, createdAt: json['created_at'] as String?, updatedAt: json['updated_at'] as String?, - internal: json['internal'] as bool, + internal: json['internal'] as bool?, servings: (json['servings'] is int) ? json['servings'] : ((json['servings'] is double) ? json['servings'].toInt() : null), servingsText: json['servings_text'] as String?, rating: (json['rating'] is int) ? json['rating'] : ((json['rating'] is double) ? json['rating'].toInt() : null), diff --git a/lib/models/step.dart b/lib/models/step.dart index a5af802..e202679 100644 --- a/lib/models/step.dart +++ b/lib/models/step.dart @@ -1,5 +1,6 @@ import 'package:untare/models/ingredient.dart'; import 'package:hive/hive.dart'; +import 'package:untare/models/recipe.dart'; part 'step.g.dart'; @@ -22,6 +23,10 @@ class StepModel { final int? time; @HiveField(7) final int? order; + @HiveField(8) + final int? stepRecipe; + @HiveField(9) + final Recipe? stepRecipeData; StepModel({ this.id, @@ -31,7 +36,9 @@ class StepModel { this.ingredientsMarkdown, this.ingredientsVue, this.time, - this.order + this.order, + this.stepRecipe, + this.stepRecipeData }); StepModel copyWith({ @@ -43,6 +50,8 @@ class StepModel { String? ingredientsVue, int? time, int? order, + int? stepRecipe, + Recipe? stepRecipeData }) { return StepModel( id: id ?? this.id, @@ -52,7 +61,8 @@ class StepModel { ingredientsMarkdown: ingredientsMarkdown ?? this.ingredientsMarkdown, ingredientsVue: ingredientsVue ?? this.ingredientsVue, time: time ?? this.time, - order: order ?? this.order + order: order ?? this.order, + stepRecipe: stepRecipe ?? this.stepRecipe ); } @@ -71,7 +81,8 @@ class StepModel { 'instruction': instruction ?? '', 'ingredients': ingredients, 'time': time ?? 0, - 'order': order ?? 0 + 'order': order ?? 0, + 'step_recipe': stepRecipe }; } @@ -85,6 +96,8 @@ class StepModel { ingredientsVue: json['ingredients_vue'] as String?, time: json['time'] as int?, order: json['order'] as int?, + stepRecipe: json['step_recipe'] as int?, + stepRecipeData: (json['step_recipe_data'] != null) ? Recipe.fromJson(json['step_recipe_data']) : null ); } } \ No newline at end of file From 4384682cd7312c3e6ac46db1490ea0044de44bc0 Mon Sep 17 00:00:00 2001 From: Tom Date: Sun, 30 Jul 2023 12:32:19 +0200 Subject: [PATCH 3/7] #28: Added timeout after 10 seconds as fallback to use cache --- lib/pages/recipes_page.dart | 6 ++++++ lib/services/api/api_service.dart | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/pages/recipes_page.dart b/lib/pages/recipes_page.dart index 52f594a..39bec45 100644 --- a/lib/pages/recipes_page.dart +++ b/lib/pages/recipes_page.dart @@ -126,6 +126,12 @@ class RecipesPageState extends State { } isLoading = false; recipes.addAll(state.recipes); + } else if (state is RecipeListFetchedFromCache) { + if (state.recipes.isEmpty || state.recipes.length < pageSize) { + isLastPage = true; + } + isLoading = false; + recipes.addAll(state.recipes); } if (state is RecipeError) { diff --git a/lib/services/api/api_service.dart b/lib/services/api/api_service.dart index 9e93118..315d56d 100644 --- a/lib/services/api/api_service.dart +++ b/lib/services/api/api_service.dart @@ -86,7 +86,7 @@ class ApiService { Future sendRequest(BaseRequest request) async { try { - Response response = await Response.fromStream(await request.send()); + Response response = await Response.fromStream(await request.send().timeout(const Duration(seconds: 10))); if (response.statusCode == 401) { box.clear(); From 81e5a6090afd2b5c5da0308b4b21bb4a950fd20b Mon Sep 17 00:00:00 2001 From: Tom Date: Sun, 30 Jul 2023 12:56:20 +0200 Subject: [PATCH 4/7] #31,#10: Add better error messages on login --- lib/blocs/login/login_bloc.dart | 3 +++ lib/exceptions/api_connection_exception.dart | 3 +++ lib/services/api/api_service.dart | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/blocs/login/login_bloc.dart b/lib/blocs/login/login_bloc.dart index 748cee5..8f8e53a 100644 --- a/lib/blocs/login/login_bloc.dart +++ b/lib/blocs/login/login_bloc.dart @@ -4,6 +4,7 @@ import 'package:bloc/bloc.dart'; import 'package:hive/hive.dart'; import 'package:untare/blocs/authentication/authentication_bloc.dart'; import 'package:untare/blocs/authentication/authentication_event.dart'; +import 'package:untare/exceptions/api_connection_exception.dart'; import 'package:untare/exceptions/api_exception.dart'; import 'package:untare/models/user.dart'; import 'package:untare/models/userToken.dart'; @@ -50,6 +51,8 @@ class LoginBloc extends Bloc { } else { emit(LoginFailure(error: e.message ?? e.toString())); } + } on ApiConnectionException catch(e) { + emit(LoginFailure(error: e.message)); } catch (err) { emit(LoginFailure(error: err.toString())); } diff --git a/lib/exceptions/api_connection_exception.dart b/lib/exceptions/api_connection_exception.dart index 681353e..537df0e 100644 --- a/lib/exceptions/api_connection_exception.dart +++ b/lib/exceptions/api_connection_exception.dart @@ -1,2 +1,5 @@ class ApiConnectionException implements Exception{ + final String message; + + ApiConnectionException({this.message = 'Api error'}); } \ No newline at end of file diff --git a/lib/services/api/api_service.dart b/lib/services/api/api_service.dart index 315d56d..1bd8f7b 100644 --- a/lib/services/api/api_service.dart +++ b/lib/services/api/api_service.dart @@ -111,7 +111,7 @@ class ApiService { ); } - throw ApiConnectionException(); + throw ApiConnectionException(message: e.toString()); } } From c78b20ce65684ce99bba66aa999c98739655f06c Mon Sep 17 00:00:00 2001 From: Tom Date: Sun, 30 Jul 2023 12:56:40 +0200 Subject: [PATCH 5/7] Generated hive models for caching --- lib/models/recipe.g.dart | 2 +- lib/models/step.g.dart | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/models/recipe.g.dart b/lib/models/recipe.g.dart index 3676ba0..58341ff 100644 --- a/lib/models/recipe.g.dart +++ b/lib/models/recipe.g.dart @@ -28,7 +28,7 @@ class RecipeAdapter extends TypeAdapter { createdBy: fields[8] as int?, createdAt: fields[9] as String?, updatedAt: fields[10] as String?, - internal: fields[11] as bool, + internal: fields[11] as bool?, servings: fields[12] as int?, servingsText: fields[13] as String?, rating: fields[14] as int?, diff --git a/lib/models/step.g.dart b/lib/models/step.g.dart index a714d60..856914a 100644 --- a/lib/models/step.g.dart +++ b/lib/models/step.g.dart @@ -25,13 +25,15 @@ class StepModelAdapter extends TypeAdapter { ingredientsVue: fields[5] as String?, time: fields[6] as int?, order: fields[7] as int?, + stepRecipe: fields[8] as int?, + stepRecipeData: fields[9] as Recipe?, ); } @override void write(BinaryWriter writer, StepModel obj) { writer - ..writeByte(8) + ..writeByte(10) ..writeByte(0) ..write(obj.id) ..writeByte(1) @@ -47,7 +49,11 @@ class StepModelAdapter extends TypeAdapter { ..writeByte(6) ..write(obj.time) ..writeByte(7) - ..write(obj.order); + ..write(obj.order) + ..writeByte(8) + ..write(obj.stepRecipe) + ..writeByte(9) + ..write(obj.stepRecipeData); } @override From f193b5685d468dcad001bd8598c0bc628bb4ebce Mon Sep 17 00:00:00 2001 From: Tom Date: Sun, 30 Jul 2023 12:58:23 +0200 Subject: [PATCH 6/7] #25: Added landscape mode --- lib/main.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 21f3520..8b25b0b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_phoenix/flutter_phoenix.dart'; @@ -61,7 +60,7 @@ void main() async{ await Workmanager().initialize(_callbackDispatcher); - SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]).then((_){ + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown, DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]).then((_){ runApp( Phoenix( child: const Tare() From 0aded12da9346d14db1389132367fb4fe4020af1 Mon Sep 17 00:00:00 2001 From: Tom Date: Sun, 30 Jul 2023 13:47:30 +0200 Subject: [PATCH 7/7] Fix #32: Changed keyboard type for url input --- lib/pages/starting_page.dart | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/starting_page.dart b/lib/pages/starting_page.dart index 7dc5cac..0e4cae0 100644 --- a/lib/pages/starting_page.dart +++ b/lib/pages/starting_page.dart @@ -152,7 +152,7 @@ class __SignInFormState extends State<_SignInForm> { hintText: 'https://recipes.excample.com' ), controller: _urlController, - keyboardType: TextInputType.name, + keyboardType: TextInputType.url, autocorrect: false, validator: (value){ if (value == null){ diff --git a/pubspec.yaml b/pubspec.yaml index c592e97..a4075d1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.1.1+3 +version: 1.1.2+4 environment: sdk: ">=2.17.0 <3.0.0"