From 671421d3cea27d96bcb1b30d1c401a37951372a3 Mon Sep 17 00:00:00 2001 From: Chirag Rami <87525902+chiragramimi@users.noreply.github.com> Date: Wed, 24 Jul 2024 16:03:11 +0530 Subject: [PATCH] feat: custom food (#16) --- .../project.pbxproj | 142 +++++----- example/ios/Podfile.lock | 4 +- example/package.json | 2 +- example/src/App.tsx | 4 +- example/src/SplashScreen.tsx | 4 +- example/yarn.lock | 8 +- package.json | 2 +- src/assets/icons/mode_barcode.png | Bin 0 -> 993 bytes src/assets/icons/mode_nutritionfact.png | Bin 0 -> 841 bytes src/assets/icons/mode_visual.png | Bin 0 -> 1001 bytes src/assets/icons/scan_barcode_not_fouind.png | Bin 0 -> 1897 bytes src/assets/index.ts | 4 + .../alternatives/alternativesItem.tsx | 33 ++- src/components/backNavigations/styles.ts | 4 +- src/components/cards/Card.tsx | 2 +- .../doughnutChart/DoughnutChart.tsx | 36 +-- .../imagePickerOptions/ImagePickerOptions.tsx | 9 +- src/components/passio/PassioFoodIcon.tsx | 55 +++- src/components/toast/toastConfig.tsx | 10 +- src/contexts/services/NutritionDataService.ts | 10 +- src/contexts/services/data/DataService.ts | 10 + src/db/DBConstant.ts | 2 + src/db/DBOperations.ts | 66 ++++- src/db/DBService.ts | 6 + src/hooks/usePassioAuthConfig.ts | 7 +- src/modals/FoodLogEditorModal/index.tsx | 3 +- src/models/FoodItem.ts | 4 +- src/models/FoodLog.ts | 10 +- src/models/Image.ts | 6 + src/models/index.tsx | 1 + .../params/NutritionNavigatorParam.ts | 1 + src/screens/advisor/view/IngredientsView.tsx | 4 +- .../view/message/records/MessageRecords.tsx | 10 +- src/screens/barcode/BarcodeScanScreen.tsx | 31 +-- src/screens/barcode/useBarcodeScan.ts | 28 +- .../editFoodLogs/editFoodLogsScreen.tsx | 62 +++-- src/screens/editFoodLogs/useEditFoodLog.ts | 63 +++-- .../editFoodLogs/utils/EditFoodLogUtils.ts | 16 +- .../views/ingredients/IngredientItemView.tsx | 3 +- .../views/logInformationsView.tsx | 68 +++-- .../editIngredients/EditIngredientScreen.tsx | 28 +- src/screens/editRecipe/useEditFoodLog.ts | 17 +- src/screens/foodCreator/FoodCreator.utils.ts | 133 ++++++++- src/screens/foodCreator/FoodCreatorScreen.tsx | 2 + src/screens/foodCreator/useFoodCreator.ts | 57 +++- .../views/FoodCreatorFoodDetail.tsx | 28 +- .../foodCreator/views/OtherNutritionFacts.tsx | 25 +- .../views/RequireNutritionFacts.tsx | 33 ++- src/screens/meallogss/useMealLogs.ts | 8 +- .../meallogss/views/MealLogItemView.tsx | 4 +- .../views/FavoriteFoodLogView.tsx | 3 +- src/screens/myFoods/useMyFoodScreen.ts | 12 +- .../myFoods/views/customFoods/CustomFoods.tsx | 5 +- .../customFoods/views/CustomFoodsItem.tsx | 11 +- src/screens/myPlans/mealPlan/useMealPlan.ts | 27 +- src/screens/quick/QuickScanningScreen.tsx | 249 ++++++++--------- src/screens/quick/mock/mockResponse.ts | 191 ------------- .../quick/mode/barcode/BarcodeFoodScan.tsx | 175 ++++++++++++ .../quick/mode/barcode/BarcodeNotDetect.tsx | 114 ++++++++ .../quick/mode/barcode/useBarcodeFoodScan.ts | 262 ++++++++++++++++++ .../mode/nutritionFact/NutritionFactScan.tsx | 94 +++++++ .../nutritionFact/useNutritionFactScan.ts | 66 +++++ .../quick/mode/visual/VisualFoodScan.tsx | 168 +++++++++++ .../visual/useVisualFoodScan.ts} | 91 ++---- src/screens/quick/views/NutritionFactView.tsx | 21 +- src/screens/quick/views/QuickScanInfo.tsx | 37 ++- .../quick/views/QuickScanLogButtonView.tsx | 38 +-- .../quick/views/QuickScanningLoadingView.tsx | 19 +- .../quick/views/QuickScanningResultView.tsx | 39 ++- .../RecipeEditor/useRecipeEditor.ts | 15 +- .../modal/EditIngredientsModal.tsx | 16 +- src/screens/recipeEditor/utils/RecipeData.ts | 8 +- .../voiceLogging/VoiceLoggingScreen.tsx | 4 +- .../voiceLogging/useVoiceLoggingScreen.ts | 24 +- .../voiceLogging/views/VoiceLoggingResult.tsx | 85 +++--- src/utils/PassioUtils.ts | 12 +- src/utils/QuickResultUtils.ts | 9 +- src/utils/V3Utils.tsx | 58 +++- src/utils/passioFoodDataInfoUtils.ts | 96 ++++++- src/utils/quickSuggestionUtils.ts | 4 +- yarn.lock | 8 +- 81 files changed, 2065 insertions(+), 961 deletions(-) create mode 100644 src/assets/icons/mode_barcode.png create mode 100644 src/assets/icons/mode_nutritionfact.png create mode 100644 src/assets/icons/mode_visual.png create mode 100644 src/assets/icons/scan_barcode_not_fouind.png create mode 100644 src/models/Image.ts delete mode 100644 src/screens/quick/mock/mockResponse.ts create mode 100644 src/screens/quick/mode/barcode/BarcodeFoodScan.tsx create mode 100644 src/screens/quick/mode/barcode/BarcodeNotDetect.tsx create mode 100644 src/screens/quick/mode/barcode/useBarcodeFoodScan.ts create mode 100644 src/screens/quick/mode/nutritionFact/NutritionFactScan.tsx create mode 100644 src/screens/quick/mode/nutritionFact/useNutritionFactScan.ts create mode 100644 src/screens/quick/mode/visual/VisualFoodScan.tsx rename src/screens/quick/{useQuickScan.ts => mode/visual/useVisualFoodScan.ts} (73%) diff --git a/example/ios/NutritionUxExample.xcodeproj/project.pbxproj b/example/ios/NutritionUxExample.xcodeproj/project.pbxproj index 7fc0188..204ecb2 100644 --- a/example/ios/NutritionUxExample.xcodeproj/project.pbxproj +++ b/example/ios/NutritionUxExample.xcodeproj/project.pbxproj @@ -15,10 +15,10 @@ 2D02E4BD1E0B4A84006451C7 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 2D02E4BF1E0B4AB3006451C7 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 2DCD954D1E0B4F2C00145EB5 /* NutritionUxExampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* NutritionUxExampleTests.m */; }; + 7D29CB763E7193742E943A95 /* libPods-NutritionUxExample-NutritionUxExampleTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C585359F35C2C736FADA47CA /* libPods-NutritionUxExample-NutritionUxExampleTests.a */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; - 91A1123569C008B54674F146 /* libPods-NutritionUxExample-NutritionUxExampleTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 57BE50A3D860EDABC5CD3030 /* libPods-NutritionUxExample-NutritionUxExampleTests.a */; }; C4287D1026D640B4003C9F7F /* passio.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4287D0F26D640B4003C9F7F /* passio.swift */; }; - DD13A759B8DF21D539271352 /* libPods-NutritionUxExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 72DB1A698DEBCC418580B5F0 /* libPods-NutritionUxExample.a */; }; + F66FD7F338E2FA1178CD51DC /* libPods-NutritionUxExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 45374D6AF3863877CFCBDCAF /* libPods-NutritionUxExample.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -43,24 +43,24 @@ 00E356EE1AD99517003FC87E /* NutritionUxExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NutritionUxExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* NutritionUxExampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NutritionUxExampleTests.m; sourceTree = ""; }; + 122FE1FC1C70D51687A15E40 /* Pods-NutritionUxExample-NutritionUxExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NutritionUxExample-NutritionUxExampleTests.release.xcconfig"; path = "Target Support Files/Pods-NutritionUxExample-NutritionUxExampleTests/Pods-NutritionUxExample-NutritionUxExampleTests.release.xcconfig"; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* NutritionUxExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NutritionUxExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = NutritionUxExample/AppDelegate.h; sourceTree = ""; }; 13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = NutritionUxExample/AppDelegate.mm; sourceTree = ""; }; 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = NutritionUxExample/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = NutritionUxExample/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = NutritionUxExample/main.m; sourceTree = ""; }; + 2A08E236D9FC93412123D6FA /* Pods-NutritionUxExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NutritionUxExample.release.xcconfig"; path = "Target Support Files/Pods-NutritionUxExample/Pods-NutritionUxExample.release.xcconfig"; sourceTree = ""; }; 2D02E47B1E0B4A5D006451C7 /* NutritionUxExample-tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "NutritionUxExample-tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 2D02E4901E0B4A5D006451C7 /* NutritionUxExample-tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "NutritionUxExample-tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - 57BE50A3D860EDABC5CD3030 /* libPods-NutritionUxExample-NutritionUxExampleTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-NutritionUxExample-NutritionUxExampleTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 65497C36A6EB87B52E57FED1 /* Pods-NutritionUxExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NutritionUxExample.release.xcconfig"; path = "Target Support Files/Pods-NutritionUxExample/Pods-NutritionUxExample.release.xcconfig"; sourceTree = ""; }; - 72DB1A698DEBCC418580B5F0 /* libPods-NutritionUxExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-NutritionUxExample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 45374D6AF3863877CFCBDCAF /* libPods-NutritionUxExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-NutritionUxExample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 76AFBAB62BDA441A00C8D9F0 /* NutritionUxExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = NutritionUxExample.entitlements; path = NutritionUxExample/NutritionUxExample.entitlements; sourceTree = ""; }; - 7D9B0715650E6E7726FC00D2 /* Pods-NutritionUxExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NutritionUxExample.debug.xcconfig"; path = "Target Support Files/Pods-NutritionUxExample/Pods-NutritionUxExample.debug.xcconfig"; sourceTree = ""; }; + 7BB6BD8FCB4707786A506D18 /* Pods-NutritionUxExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NutritionUxExample.debug.xcconfig"; path = "Target Support Files/Pods-NutritionUxExample/Pods-NutritionUxExample.debug.xcconfig"; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = NutritionUxExample/LaunchScreen.storyboard; sourceTree = ""; }; - 96FF7857AE7DFCBDFD988E7A /* Pods-NutritionUxExample-NutritionUxExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NutritionUxExample-NutritionUxExampleTests.debug.xcconfig"; path = "Target Support Files/Pods-NutritionUxExample-NutritionUxExampleTests/Pods-NutritionUxExample-NutritionUxExampleTests.debug.xcconfig"; sourceTree = ""; }; - C240E7D476E9C9E1B4425433 /* Pods-NutritionUxExample-NutritionUxExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NutritionUxExample-NutritionUxExampleTests.release.xcconfig"; path = "Target Support Files/Pods-NutritionUxExample-NutritionUxExampleTests/Pods-NutritionUxExample-NutritionUxExampleTests.release.xcconfig"; sourceTree = ""; }; + AE69FF3F6EB474081404D2DF /* Pods-NutritionUxExample-NutritionUxExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NutritionUxExample-NutritionUxExampleTests.debug.xcconfig"; path = "Target Support Files/Pods-NutritionUxExample-NutritionUxExampleTests/Pods-NutritionUxExample-NutritionUxExampleTests.debug.xcconfig"; sourceTree = ""; }; C4287D0E26D640B4003C9F7F /* NutritionUxExample-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NutritionUxExample-Bridging-Header.h"; sourceTree = ""; }; C4287D0F26D640B4003C9F7F /* passio.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = passio.swift; sourceTree = ""; }; + C585359F35C2C736FADA47CA /* libPods-NutritionUxExample-NutritionUxExampleTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-NutritionUxExample-NutritionUxExampleTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; ED2971642150620600B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.0.sdk/System/Library/Frameworks/JavaScriptCore.framework; sourceTree = DEVELOPER_DIR; }; /* End PBXFileReference section */ @@ -70,7 +70,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 91A1123569C008B54674F146 /* libPods-NutritionUxExample-NutritionUxExampleTests.a in Frameworks */, + 7D29CB763E7193742E943A95 /* libPods-NutritionUxExample-NutritionUxExampleTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -78,7 +78,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DD13A759B8DF21D539271352 /* libPods-NutritionUxExample.a in Frameworks */, + F66FD7F338E2FA1178CD51DC /* libPods-NutritionUxExample.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -136,8 +136,8 @@ children = ( ED297162215061F000B7C4FE /* JavaScriptCore.framework */, ED2971642150620600B7C4FE /* JavaScriptCore.framework */, - 72DB1A698DEBCC418580B5F0 /* libPods-NutritionUxExample.a */, - 57BE50A3D860EDABC5CD3030 /* libPods-NutritionUxExample-NutritionUxExampleTests.a */, + 45374D6AF3863877CFCBDCAF /* libPods-NutritionUxExample.a */, + C585359F35C2C736FADA47CA /* libPods-NutritionUxExample-NutritionUxExampleTests.a */, ); name = Frameworks; sourceTree = ""; @@ -180,10 +180,10 @@ C3549CF536FDCB5027930FAF /* Pods */ = { isa = PBXGroup; children = ( - 7D9B0715650E6E7726FC00D2 /* Pods-NutritionUxExample.debug.xcconfig */, - 65497C36A6EB87B52E57FED1 /* Pods-NutritionUxExample.release.xcconfig */, - 96FF7857AE7DFCBDFD988E7A /* Pods-NutritionUxExample-NutritionUxExampleTests.debug.xcconfig */, - C240E7D476E9C9E1B4425433 /* Pods-NutritionUxExample-NutritionUxExampleTests.release.xcconfig */, + 7BB6BD8FCB4707786A506D18 /* Pods-NutritionUxExample.debug.xcconfig */, + 2A08E236D9FC93412123D6FA /* Pods-NutritionUxExample.release.xcconfig */, + AE69FF3F6EB474081404D2DF /* Pods-NutritionUxExample-NutritionUxExampleTests.debug.xcconfig */, + 122FE1FC1C70D51687A15E40 /* Pods-NutritionUxExample-NutritionUxExampleTests.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -195,12 +195,12 @@ isa = PBXNativeTarget; buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "NutritionUxExampleTests" */; buildPhases = ( - 549E47C951F80D164E9116DE /* [CP] Check Pods Manifest.lock */, + C0437C251212BC6E839D7D62 /* [CP] Check Pods Manifest.lock */, 00E356EA1AD99517003FC87E /* Sources */, 00E356EB1AD99517003FC87E /* Frameworks */, 00E356EC1AD99517003FC87E /* Resources */, - 6DCD1771B61D360896BAFD83 /* [CP] Embed Pods Frameworks */, - 656F91D027CC4FE5151E4484 /* [CP] Copy Pods Resources */, + 0A7EA2FDDB0532E126EB900E /* [CP] Embed Pods Frameworks */, + 08E2782204A1D3D15FD50500 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -216,13 +216,13 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "NutritionUxExample" */; buildPhases = ( - BBB35D205212C4BD449079C6 /* [CP] Check Pods Manifest.lock */, + 2DA77CB15BC437C42FA4B290 /* [CP] Check Pods Manifest.lock */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - B4694309F5A9204733F25785 /* [CP] Embed Pods Frameworks */, - CAB65B4BF8AFDA30E5806D35 /* [CP] Copy Pods Resources */, + 3FD9A21DD2DF497BE6DF8A46 /* [CP] Embed Pods Frameworks */, + 83A174EA98E9F090704EA621 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -369,81 +369,81 @@ shellPath = /bin/sh; shellScript = "set -e\n\nWITH_ENVIRONMENT=\"../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../node_modules/react-native/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n"; }; - 2D02E4CB1E0B4B27006451C7 /* Bundle React Native Code And Images */ = { + 08E2782204A1D3D15FD50500 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-NutritionUxExample-NutritionUxExampleTests/Pods-NutritionUxExample-NutritionUxExampleTests-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle", ); - name = "Bundle React Native Code And Images"; + name = "[CP] Copy Pods Resources"; outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NutritionUxExample-NutritionUxExampleTests/Pods-NutritionUxExample-NutritionUxExampleTests-resources.sh\"\n"; + showEnvVarsInLog = 0; }; - 549E47C951F80D164E9116DE /* [CP] Check Pods Manifest.lock */ = { + 0A7EA2FDDB0532E126EB900E /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-NutritionUxExample-NutritionUxExampleTests/Pods-NutritionUxExample-NutritionUxExampleTests-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/ReactNativePassioSDK/PassioNutritionAISDK.framework/PassioNutritionAISDK", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", ); + name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-NutritionUxExample-NutritionUxExampleTests-checkManifestLockResult.txt", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PassioNutritionAISDK.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NutritionUxExample-NutritionUxExampleTests/Pods-NutritionUxExample-NutritionUxExampleTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 656F91D027CC4FE5151E4484 /* [CP] Copy Pods Resources */ = { + 2D02E4CB1E0B4B27006451C7 /* Bundle React Native Code And Images */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-NutritionUxExample-NutritionUxExampleTests/Pods-NutritionUxExample-NutritionUxExampleTests-resources.sh", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle", ); - name = "[CP] Copy Pods Resources"; + name = "Bundle React Native Code And Images"; outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NutritionUxExample-NutritionUxExampleTests/Pods-NutritionUxExample-NutritionUxExampleTests-resources.sh\"\n"; - showEnvVarsInLog = 0; + shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh"; }; - 6DCD1771B61D360896BAFD83 /* [CP] Embed Pods Frameworks */ = { + 2DA77CB15BC437C42FA4B290 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-NutritionUxExample-NutritionUxExampleTests/Pods-NutritionUxExample-NutritionUxExampleTests-frameworks.sh", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/ReactNativePassioSDK/PassioNutritionAISDK.framework/PassioNutritionAISDK", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PassioNutritionAISDK.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", + "$(DERIVED_FILE_DIR)/Pods-NutritionUxExample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NutritionUxExample-NutritionUxExampleTests/Pods-NutritionUxExample-NutritionUxExampleTests-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - B4694309F5A9204733F25785 /* [CP] Embed Pods Frameworks */ = { + 3FD9A21DD2DF497BE6DF8A46 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -463,44 +463,44 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NutritionUxExample/Pods-NutritionUxExample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - BBB35D205212C4BD449079C6 /* [CP] Check Pods Manifest.lock */ = { + 83A174EA98E9F090704EA621 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-NutritionUxExample/Pods-NutritionUxExample-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle", ); + name = "[CP] Copy Pods Resources"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-NutritionUxExample-checkManifestLockResult.txt", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NutritionUxExample/Pods-NutritionUxExample-resources.sh\"\n"; showEnvVarsInLog = 0; }; - CAB65B4BF8AFDA30E5806D35 /* [CP] Copy Pods Resources */ = { + C0437C251212BC6E839D7D62 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-NutritionUxExample/Pods-NutritionUxExample-resources.sh", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle", + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "[CP] Copy Pods Resources"; outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", + "$(DERIVED_FILE_DIR)/Pods-NutritionUxExample-NutritionUxExampleTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NutritionUxExample/Pods-NutritionUxExample-resources.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -559,7 +559,7 @@ /* Begin XCBuildConfiguration section */ 00E356F61AD99517003FC87E /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 96FF7857AE7DFCBDFD988E7A /* Pods-NutritionUxExample-NutritionUxExampleTests.debug.xcconfig */; + baseConfigurationReference = AE69FF3F6EB474081404D2DF /* Pods-NutritionUxExample-NutritionUxExampleTests.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; @@ -583,7 +583,7 @@ }; 00E356F71AD99517003FC87E /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C240E7D476E9C9E1B4425433 /* Pods-NutritionUxExample-NutritionUxExampleTests.release.xcconfig */; + baseConfigurationReference = 122FE1FC1C70D51687A15E40 /* Pods-NutritionUxExample-NutritionUxExampleTests.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; @@ -604,7 +604,7 @@ }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7D9B0715650E6E7726FC00D2 /* Pods-NutritionUxExample.debug.xcconfig */; + baseConfigurationReference = 7BB6BD8FCB4707786A506D18 /* Pods-NutritionUxExample.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -639,7 +639,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 65497C36A6EB87B52E57FED1 /* Pods-NutritionUxExample.release.xcconfig */; + baseConfigurationReference = 2A08E236D9FC93412123D6FA /* Pods-NutritionUxExample.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 2267790..b2520a9 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1083,7 +1083,7 @@ PODS: - React-Core - RNCAsyncStorage (1.21.0): - React-Core - - RNCPicker (2.6.1): + - RNCPicker (2.7.7): - React-Core - RNDateTimePicker (7.6.3): - React-Core @@ -1405,7 +1405,7 @@ SPEC CHECKSUMS: ReactCommon: 45b5d4f784e869c44a6f5a8fad5b114ca8f78c53 ReactNativePassioSDK: 058706b380c8a0de2c73fb11fa2589f4bae60463 RNCAsyncStorage: 618d03a5f52fbccb3d7010076bc54712844c18ef - RNCPicker: b18aaf30df596e9b1738e7c1f9ee55402a229dca + RNCPicker: b7873ba797dc586bfaf3307d737cbdc620a9ff3e RNDateTimePicker: 7b38b71bcd7c4cfa1cb95f2dff9a4f1faed2dced RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: 727b656d5a3fa247aac2c8cc70b8757356066451 diff --git a/example/package.json b/example/package.json index e9119ac..e218310 100644 --- a/example/package.json +++ b/example/package.json @@ -22,7 +22,7 @@ "@react-native-async-storage/async-storage": "^1.21.0", "@react-native-community/datetimepicker": "^7.6.3", "@react-native-community/slider": "^4.5.0", - "@react-native-picker/picker": "^2.6.1", + "@react-native-picker/picker": "^2.7.7", "@react-native-voice/voice": "^3.2.4", "@react-navigation/bottom-tabs": "^6.5.20", "@react-navigation/native": "^6.1.17", diff --git a/example/src/App.tsx b/example/src/App.tsx index 158f53d..08e04bb 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -11,10 +11,10 @@ import { SplashScreen } from './SplashScreen'; import { ENV_PASSIO_KEY } from '@env'; export default function App() { - const { isReady } = usePassioConfig({ key: ENV_PASSIO_KEY }); + const { isReady, error } = usePassioConfig({ key: ENV_PASSIO_KEY }); if (!isReady) { - return ; + return ; } return ( diff --git a/example/src/SplashScreen.tsx b/example/src/SplashScreen.tsx index fd304c1..4301382 100644 --- a/example/src/SplashScreen.tsx +++ b/example/src/SplashScreen.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Image, ImageBackground, StyleSheet, Text, View } from 'react-native'; -export const SplashScreen = () => { +export const SplashScreen = ({ error }: { error: string | undefined }) => { return ( { /> - {'Please wait...\nSDK Configuring...'} + {error ? error : 'Please wait...\nSDK Configuring...'} diff --git a/example/yarn.lock b/example/yarn.lock index fec8a46..4a289db 100644 --- a/example/yarn.lock +++ b/example/yarn.lock @@ -1626,10 +1626,10 @@ resolved "https://registry.yarnpkg.com/@react-native-community/slider/-/slider-4.5.0.tgz#5c55488ee30060cd87100fb746b9d8655dbab04e" integrity sha512-pyUvNTvu5IfCI5abzqRfO/dd3A009RC66RXZE6t0gyOwI/j0QDlq9VZRv3rjkpuIvNTnsYj+m5BHlh0DkSYUyA== -"@react-native-picker/picker@^2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@react-native-picker/picker/-/picker-2.6.1.tgz#3b20ddd1385fab0487db103dc6519570f8892e6d" - integrity sha512-oJftvmLOj6Y6/bF4kPcK6L83yNBALGmqNYugf94BzP0FQGpHBwimVN2ygqkQ2Sn2ZU3pGUZMs0jV6+Gku2GyYg== +"@react-native-picker/picker@^2.7.7": + version "2.7.7" + resolved "https://registry.yarnpkg.com/@react-native-picker/picker/-/picker-2.7.7.tgz#6e19c3a72a482be015f5194794e6c14efe8762e8" + integrity sha512-CTHthVmx8ujlH/u5AnxLQfsheh/DoEbo+Kbx0HGTlbKVLC1eZ4Kr9jXIIUcwB7JEgOXifdZIPQCsoTc/7GQ0ag== "@react-native-voice/voice@^3.2.4": version "3.2.4" diff --git a/package.json b/package.json index 95ec9ae..f158765 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "@react-native-async-storage/async-storage": "^1.21.0", "@react-native-community/datetimepicker": "^7.6.3", "@react-native-community/slider": "^4.5.0", - "@react-native-picker/picker": "^2.6.1", + "@react-native-picker/picker": "^2.7.7", "@react-native-voice/voice": "^3.2.4", "@react-navigation/bottom-tabs": "^6.5.20", "@react-navigation/native": "^6.1.17", diff --git a/src/assets/icons/mode_barcode.png b/src/assets/icons/mode_barcode.png new file mode 100644 index 0000000000000000000000000000000000000000..3a60a06b00b15292ea097135c2e1f89332511ab4 GIT binary patch literal 993 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGooCO|{#S9EO-XP4l)OOlRpde#$ zkh>GZx^prwfgF}}M_)$E)e-c?47?{gET^vIy7~kG~UwGL-g6)I4%>7LD6~>M) zSuBJD)D(O!7%;wN$yHl0x53EqEXx#51K}k;32}+4&)I(NUoyRW{{6Xn;_+P_^CF6B zmYXvO!oWq5GX_C{BJQ6)MBQ4sW#^V~i=6#Sa+my_@XccO*?9(s9x$Xl2(J9mQuLwU zAolw*Pl0lY1?3D=8~q(acWybo&#{E>fS+mFgYX4v=9kv(^7Ea`G~uBClZV`=UbfwO zdPz*k>E5?P*BIVBu=ntGsC{zj+^ush;u-07KRH*dXL)+VG>gZah@gHeH|(3BZ>QT(+q!4 z`!{cI`peHUvg@wf&U_!wyK~tB7O!LXZpnHqJ}(>25WH`zOw^|O1*g|`=oMwYU!p6J zpLklU_MURz*0-0p$jI$!k-utPKl99Lb&hJy+1~Ztr#_$2D^fgvFZyNi_6G0z{!brj zbLQKfYdG`K)cQ;01k1i!*NFS8{NGfcGn}+uGh>yT{sSHdN#=lK3{wmlQYmEqGna;A!y3&sMSF&rSB}hu_fBx-jQ*?xNBm~8-9cI1I>Th?QT6!Q*e5}$!5~OGyB$U zefyv3;r6hZo0!vrAI^RISxWB5tpyVm?zwGWTfF0Z%Yabd|lO?mGx zO!Z~jYo^uwKVZ*zW3BndZF$SCsvB8|bL8LrWHUwmy4jswrU!Byyan%_s#7}qPdj+J zh8x56OV3xny&R*nq~r0$(hcjPx^D8mNS(TT?$XtZl42PQPmB5-y%W0SRY%}77C;_?G&=Tq191Fs$QaR`+> z#cKZgvwL#gfyB;DLJ4o85B(Q5ww?WQ+4lqaKmTz~PR>*Zru#s#tzSP0tGU3z&l%#jS9u6{1-oD!MGZx^prwfgF}}M_)$E)e-c?47?|#Px;TbZFuuL}v2eD52-}C}aqJ)1RHajz zcPW=NSqX2Mr*M+RSG{AmqZMOHQwsBD$4i`7mN)%BIrnP^YmLp1x^k=Md^;rUqCGgc z1(m=+>Z;c{0jC{Trh9)}eC^YosI_rNjcq?%kBV!G{~pElQEgwQu79H4x>ThzS|0b` zM8(hBB4VU(y>`+&mNniC)wMs>K9?+1yuvhvN5SOlQ^#ko9hrYju;hX~e2Lk0sjhGPr|z}B69%f4sZ(lFC_2qrVe{or6C(aOpZRWJ{3{r5zhl_Y;_*3eZu}? zje17U%DKKrU$gFvvfm=UQdI4}@HN>LNAiPrYzq5Or1>bU#_0a`ZiZXxu~#0Q&Yd21 z=gE0S_jBi1)9%(kd6tz?t#{+(m#de)%zn1=@E3NEkAm^@W}ms+lG#?k;cfB5eb?sM z3I0b6rx{h$IK=4PeQEJF^^chhlbdAQ{IcR-w-mM=oENv8f8)IUuU{Y7o!Pd}J>%6Q rr(b(hpZ!0qHm%OuRuBXxR{v+n>szu#u)YSASr|NB{an^LB{Ts5-lJyC literal 0 HcmV?d00001 diff --git a/src/assets/icons/mode_visual.png b/src/assets/icons/mode_visual.png new file mode 100644 index 0000000000000000000000000000000000000000..25f07c8bf4bce04cbb84fae3c3b48167e02f2709 GIT binary patch literal 1001 zcmV@~0drDELIAGL9O(c600d`2O+f$vv5yP^{TaXmjaz6Z zRZz?^JQ80d!P}{&vaD`5aixw`i4e9*g9@tSN$Fv z60g2)|EP7D2wcTPT<27h$tDXC>xe}RLGJa%O7<*->}Sr#&Wn}QSxAof#1ONz^TO?zFfAlUXM2?w%Ij}D3Tt?i*$qL62vOl-0@{twd<6b(6n(86n5FfH| zy1BQfc#kmkD# zVY0WxwYBQ5tmF&Ar7$;iPD7|=T_L(o?UIpR>tzCTof1_`_g)>j*F=@t@iW3GL~on^ zYwxA^%$-^_aEtMC>vV~$^o7%SDKG>yPEn>95xn24-6dO8LyBJmL@aE)(4UeT7 zuGaR==zsigBC4!qNrxkA--^16rb_H7*;(O|d}tz6H&IZxIFC(2G#s;!FiUmJ4Da9{ X2rPCf+r&pB00000NkvXXu0mjf4{OO9 literal 0 HcmV?d00001 diff --git a/src/assets/icons/scan_barcode_not_fouind.png b/src/assets/icons/scan_barcode_not_fouind.png new file mode 100644 index 0000000000000000000000000000000000000000..2caf0d902074ed586c9fe165390ed8ab75b87ef3 GIT binary patch literal 1897 zcmV-v2bTDWP)@~0drDELIAGL9O(c600d`2O+f$vv5yPMCC+#d`KTEe)E=3FsC=`Rx-U;VMCFz0gOU)H zH|z*XLR4O`CnyO~InS=3q&!g(4aC=>n5k_v`h6xIksb=8G9G^{%lv4f1SNSNku

@{0T}zRF(59 zC<#$j%DR1!p$3xe7PQDugp(jcl#5R?i;r3^u-LR3l+ zR2GOT6@tnNQEi2w@<3FH5L8}>Di(q&0#R*1P(>lC6$q*fh-wKzrStZS##bu|bo14NY$L1l!fwnI=kAgW{tDknr04?z{+z;S<^3`0;R z?Z9z=2+B0ikNZPVRq%M>C{`!Mr>_gmdiM%Wff=@-fGd9XrIFHZ*iX{SpZvk zHc}j^*kGVOQW=7vY~^TF2+C#-M&*1^uFrF&x{vC8+Rbc<%KZ__^?5E8oWFG?D))nO zeV$8|BT=~@ln^iGSg{?dnSv*o9C|6k;6@qe^g;60W z*I5=-Tu@^Ix7ZWaI4-CmfqU$TYPb>9oakQyH&q=K=QkTcamEG&?y4{<5z}BJsBkhl zC#*-gt)i&rv)RQ)P>sQ0Jm>r)f!j<+#d%M*6FY4X%w}fvh^oNBq^A#`7n=A6|L83tVR&uJo8O@z#U09$c2+C7cMTMX| zmLn>qPkq_?5CmQ`9TkGw0VbnDP&>j@R0wK^n1~8N?HJurA*dasGb#kNqjW`upmvy! zsBkFy4r`pB6Fqs-CG|6trVc = ( - alternate: QuickResult -) => { +interface Props { + alternate: QuickResult; + onLogPress: (result: QuickResult) => void; +} + +const AlternateFoodLogView = ({ alternate, onLogPress }: Props) => { const styles = alternateFoodLogViewStyle(useBranding()); return ( @@ -25,7 +29,7 @@ const AlternateFoodLogView: React.FC = ( = ( {alternate.name} + { + onLogPress(alternate); + }} + > + + ); }; @@ -66,6 +88,7 @@ const alternateFoodLogViewStyle = ({ indigo50 }: Branding) => textConainer: { alignSelf: 'center', textTransform: 'capitalize', + flex: 1, marginHorizontal: 16, }, }); diff --git a/src/components/backNavigations/styles.ts b/src/components/backNavigations/styles.ts index b97119c..7b1d4de 100644 --- a/src/components/backNavigations/styles.ts +++ b/src/components/backNavigations/styles.ts @@ -46,8 +46,8 @@ const headerStyle = ({}: Branding, insets: EdgeInsets) => { height: scaleHeight(28), }, rightIcon: { - width: scaleWidth(20), - height: scaleHeight(20), + width: scaleWidth(24), + height: scaleHeight(24), }, }); }; diff --git a/src/components/cards/Card.tsx b/src/components/cards/Card.tsx index 08b02fc..3fd85c0 100644 --- a/src/components/cards/Card.tsx +++ b/src/components/cards/Card.tsx @@ -17,7 +17,7 @@ const cardStyle = ({ card }: Branding) => { return StyleSheet.create({ container: { backgroundColor: card, - borderRadius: 12, + borderRadius: 8, shadowColor: '#00000029', shadowOpacity: 1, shadowOffset: { diff --git a/src/components/doughnutChart/DoughnutChart.tsx b/src/components/doughnutChart/DoughnutChart.tsx index 811c8a7..09a2dc5 100644 --- a/src/components/doughnutChart/DoughnutChart.tsx +++ b/src/components/doughnutChart/DoughnutChart.tsx @@ -57,23 +57,25 @@ const DoughnutChart: React.FC = ({ return ( - - {data.map((item, index) => { - const path = calculatePath(item.progress, index); - startAngle += 360 * (item.progress / totalProgress); - return ( - - - - ); - })} - + {totalProgress > 0 && ( + + {data.map((item, index) => { + const path = calculatePath(item.progress, index); + startAngle += 360 * (item.progress / totalProgress); + return ( + + + + ); + })} + + )} ); }; diff --git a/src/components/imagePickerOptions/ImagePickerOptions.tsx b/src/components/imagePickerOptions/ImagePickerOptions.tsx index 48c89c6..a2f0133 100644 --- a/src/components/imagePickerOptions/ImagePickerOptions.tsx +++ b/src/components/imagePickerOptions/ImagePickerOptions.tsx @@ -9,7 +9,6 @@ import { } from 'react-native'; import { ICONS } from '../../assets'; import { COLORS } from '../../constants'; -import { screenHeight } from '../../utils'; interface Props { onCloseModel: () => void; @@ -70,8 +69,12 @@ const styles = StyleSheet.create({ }, modalContent: { padding: 20, - margin: 24, - top: screenHeight / 3.5, + margin: 2, + bottom: 0, + left: 0, + right: 0, + paddingBottom: 42, + position: 'absolute', backgroundColor: 'white', borderRadius: 10, alignItems: 'center', diff --git a/src/components/passio/PassioFoodIcon.tsx b/src/components/passio/PassioFoodIcon.tsx index 3d47834..db1eebe 100644 --- a/src/components/passio/PassioFoodIcon.tsx +++ b/src/components/passio/PassioFoodIcon.tsx @@ -3,10 +3,19 @@ import { type PassioID, PassioIconView, } from '@passiolife/nutritionai-react-native-sdk-v3/src/sdk/v2'; -import { Image, type ImageStyle, type StyleProp } from 'react-native'; -import React from 'react'; +import { + Image, + ImageSourcePropType, + type ImageStyle, + type StyleProp, +} from 'react-native'; + +import React, { useEffect, useState } from 'react'; import type { PassioIconType } from '../../models'; +import { CUSTOM_USER_FOOD } from '../../screens/foodCreator/FoodCreator.utils'; +import { useServices } from '../../contexts'; +import { ICONS } from '../../assets'; interface Props { style?: StyleProp; @@ -14,32 +23,64 @@ interface Props { imageName?: string; userFoodImage?: string; iconID?: string; + extra?: string; size?: IconSize; entityType?: PassioIconType; + defaultImage?: ImageSourcePropType | undefined; } /* PassioFoodIcon: RENDER FOOD IMAGE From Server */ + export const PassioFoodIcon = (props: Props) => { - const { passioID, imageName, size, userFoodImage, iconID } = props; + const { passioID, imageName, size, iconID, extra, defaultImage } = props; + const [base64, setBase64] = useState(''); + const { dataService } = useServices(); + + useEffect(() => { + async function init() { + if (iconID && iconID.includes(CUSTOM_USER_FOOD)) { + const image = await dataService.getImage(iconID); + if (image) { + setBase64(image.base64); + } + } + } + init(); + }, [dataService, iconID, extra]); + + let icon = iconID || passioID || imageName; + return ( <> - {userFoodImage ? ( + {base64 ? ( - ) : ( + ) : icon ? ( + ) : ( + )} ); diff --git a/src/components/toast/toastConfig.tsx b/src/components/toast/toastConfig.tsx index f969761..166958d 100644 --- a/src/components/toast/toastConfig.tsx +++ b/src/components/toast/toastConfig.tsx @@ -1,7 +1,13 @@ import React from 'react'; import { StyleSheet, View } from 'react-native'; -import { scaledSize, scaleHeight, scaleWidth, screenWidth } from '../../utils'; +import { + scaledSize, + scaleHeight, + scaleWidth, + screenHeight, + screenWidth, +} from '../../utils'; import { Text } from '../texts'; @@ -47,7 +53,7 @@ export const styles = StyleSheet.create({ borderColor: 'rgba(0, 0, 0, 0.6)', borderRadius: scaledSize(7), borderWidth: 0.2, - marginBottom: 120, + marginBottom: screenHeight / 3, flexDirection: 'row', height: scaledSize(50), overflow: 'hidden', diff --git a/src/contexts/services/NutritionDataService.ts b/src/contexts/services/NutritionDataService.ts index 251bf80..aafc806 100644 --- a/src/contexts/services/NutritionDataService.ts +++ b/src/contexts/services/NutritionDataService.ts @@ -1,5 +1,11 @@ import type { Water } from 'src/models/Water'; -import type { CustomFood, NutritionProfile, Recipe } from '../../models'; +import type { + CustomFood, + CustomImageID, + Image, + NutritionProfile, + Recipe, +} from '../../models'; import type { FoodLog, FavoriteFoodItem } from '../../models'; import type { PatientProfile } from '../../models'; import type { Weight } from '../../models/Weight'; @@ -27,4 +33,6 @@ export interface NutritionDataService { saveWater: (water: Water) => Promise; getWeight: (startDate: Date, endDate: Date) => Promise; saveWeight: (weight: Weight) => Promise; + saveImage: (image: Image) => Promise; + getImage: (id: CustomImageID) => Promise; } diff --git a/src/contexts/services/data/DataService.ts b/src/contexts/services/data/DataService.ts index 5fc3d9a..aa170a1 100644 --- a/src/contexts/services/data/DataService.ts +++ b/src/contexts/services/data/DataService.ts @@ -21,10 +21,14 @@ import { getCustomFoods, saveCustomFood, deleteCustomFood, + getImage, + saveImage, } from '../../../db'; import { ActivityLevelType, CustomFood, + CustomImageID, + Image, UnitSystem, type FavoriteFoodItem, type FoodLog, @@ -109,6 +113,12 @@ const dataService: NutritionDataService = { getRecipes: function (): Promise { return getRecipes(); }, + getImage: function (id: CustomImageID): Promise { + return getImage(id); + }, + saveImage: async function (image: Image): Promise { + return saveImage(await DBHandler.getInstance(), image); + }, getNutritionProfile: (): Promise => { return new Promise((resolve, reject) => { AsyncStorage.getItem('nutritionProfile') diff --git a/src/db/DBConstant.ts b/src/db/DBConstant.ts index 5499d86..05ccaad 100644 --- a/src/db/DBConstant.ts +++ b/src/db/DBConstant.ts @@ -1,6 +1,7 @@ // nutrition tables export const TABLE_FOOD_LOGS = 'food_logs'; +export const TABLE_IMAGES = 'images_table'; export const TABLE_CUSTOM_FOOD_LOGS = 'custom_food_logs'; export const TABLE_WATER = 'water'; export const TABLE_WEIGHT = 'weight'; @@ -9,6 +10,7 @@ export const TABLE_RECIPE = 'recipe_table'; // nutrition food_logs rows export const ROW_UUID = 'uuid'; +export const ROW_BASE_64 = 'base64'; export const ROW_IMAGE_NAME = 'imageName'; export const ROW_ICON_ID = 'iconID'; export const ROW_EVENT_TIME_STAMP = 'eventTimestamp'; diff --git a/src/db/DBOperations.ts b/src/db/DBOperations.ts index fe880d3..94be7f1 100644 --- a/src/db/DBOperations.ts +++ b/src/db/DBOperations.ts @@ -1,12 +1,15 @@ import type { CustomFood, + CustomImageID, FavoriteFoodItem, FoodLog, + Image, Recipe, Water, } from './../models'; import { ROW_BARCODE, + ROW_BASE_64, ROW_BRAND_NAME, ROW_COMPUTED_WEIGHT, ROW_CONSUMED, @@ -32,6 +35,7 @@ import { TABLE_CUSTOM_FOOD_LOGS, TABLE_FAVOURITE_FOOD_LOGS, TABLE_FOOD_LOGS, + TABLE_IMAGES, TABLE_RECIPE, TABLE_WATER, TABLE_WEIGHT, @@ -58,15 +62,15 @@ export const saveFoodLog = async ( foodLog.uuid, foodLog.name, foodLog.meal, - foodLog.imageName, + undefined, foodLog.entityType, foodLog.eventTimestamp, - foodLog.userFoodImage, + undefined, foodLog.iconID, JSON.stringify(foodLog.foodItems), JSON.stringify(foodLog.servingSizes), JSON.stringify(foodLog.servingUnits), - foodLog.passioID, + undefined, foodLog.selectedUnit, foodLog.selectedQuantity, ], @@ -94,7 +98,7 @@ export const saveCustomFood = async ( [ foodLog.uuid, foodLog.name, - foodLog.imageName, + undefined, foodLog.entityType, foodLog.barcode, foodLog.brandName, @@ -119,6 +123,47 @@ export const saveCustomFood = async ( }); }; +export const saveImage = async ( + db: SQLiteDatabase, + image: Image +): Promise => { + return new Promise((resolve, reject) => { + const insertQuery = `INSERT or REPLACE INTO ${TABLE_IMAGES} (${ROW_UUID}, ${ROW_BASE_64}) VALUES (?,?)`; + + db.transaction((tx) => { + tx.executeSql( + insertQuery, + [image.id, image.base64], + () => { + resolve(image.id); + }, + (_, error) => { + console.error(`Failed to save image ${error}`); + reject(error); + } + ); + }); + }); +}; +export const getImage = async ( + id: CustomImageID +): Promise => { + return new Promise(async (resolve, reject) => { + try { + const db = await DBHandler.getInstance(); + const results = await db.executeSql( + `SELECT * FROM ${TABLE_IMAGES} WHERE ${ROW_UUID} = ?`, + [id] + ); + resolve(convertResultToImage(results)?.[0]); + } catch (error) { + console.error(`Failed to get image ${error} ========= ${id}`); + reject(`Failed to get image ${error} ========= ${id}`); + throw error; + } + }); +}; + // Delete Food logs into local storage export const deleteFoodLog = async ( db: SQLiteDatabase, @@ -199,12 +244,12 @@ export const saveFavouriteFood = async ( favoriteFoodItem.uuid, favoriteFoodItem.name, favoriteFoodItem.meal, - favoriteFoodItem.imageName, + undefined, favoriteFoodItem.entityType, JSON.stringify(favoriteFoodItem.foodItems), JSON.stringify(favoriteFoodItem.servingSizes), JSON.stringify(favoriteFoodItem.servingUnits), - favoriteFoodItem.passioID, + favoriteFoodItem.iconID ?? undefined, favoriteFoodItem.selectedUnit, favoriteFoodItem.selectedQuantity, ], @@ -395,6 +440,15 @@ export function convertResultToFoodLog(results: [ResultSet]): FoodLog[] { }); return items; } +export function convertResultToImage(results: [ResultSet]): Image[] { + const items: Image[] = []; + results.forEach((result) => { + for (let index = 0; index < result.rows.length; index++) { + items.push(result.rows.item(index)); + } + }); + return items; +} export function convertResultToFavoriteFoodItem( results: [ResultSet] diff --git a/src/db/DBService.ts b/src/db/DBService.ts index 12d9d54..b4ab35f 100644 --- a/src/db/DBService.ts +++ b/src/db/DBService.ts @@ -6,6 +6,7 @@ import { } from 'react-native-sqlite-storage'; import { ROW_BARCODE, + ROW_BASE_64, ROW_BRAND_NAME, ROW_COMPUTED_WEIGHT, ROW_CONSUMED, @@ -31,6 +32,7 @@ import { TABLE_CUSTOM_FOOD_LOGS, TABLE_FAVOURITE_FOOD_LOGS, TABLE_FOOD_LOGS, + TABLE_IMAGES, TABLE_RECIPE, TABLE_WATER, TABLE_WEIGHT, @@ -77,6 +79,10 @@ export const createTable = async (db: SQLiteDatabase): Promise => { `CREATE TABLE IF NOT EXISTS ${TABLE_WEIGHT} (${ROW_UUID} TEXT PRIMARY KEY, ${ROW_WEIGHT} TEXT, ${ROW_DAY} TEXT, ${ROW_TIME} TEXT)`, [] ); + txn.executeSql( + `CREATE TABLE IF NOT EXISTS ${TABLE_IMAGES} (${ROW_UUID} TEXT PRIMARY KEY, ${ROW_BASE_64} TEXT)`, + [] + ); }, (error: SQLError) => { reject(error); diff --git a/src/hooks/usePassioAuthConfig.ts b/src/hooks/usePassioAuthConfig.ts index af5d069..44743e6 100644 --- a/src/hooks/usePassioAuthConfig.ts +++ b/src/hooks/usePassioAuthConfig.ts @@ -10,6 +10,7 @@ export function usePassioConfig({ key }: { key: string }) { const [isReady, setIsReady] = useState(false); const [leftFile, setDownloadingLeft] = useState(null); const [downloadError, setDownloadError] = useState(null); + const [error, setError] = useState(undefined); useEffect(() => { async function getAuth() { @@ -18,8 +19,11 @@ export function usePassioConfig({ key }: { key: string }) { autoUpdate: true, debugMode: true, }); - + setError(undefined); setIsReady(passioSDKStatus.mode === 'isReadyForDetection'); + if (passioSDKStatus.mode === 'error') { + setError(passioSDKStatus.errorMessage); + } } getAuth(); @@ -45,6 +49,7 @@ export function usePassioConfig({ key }: { key: string }) { isReady, leftFile, downloadError, + error, requestCameraAuthorization, }; } diff --git a/src/modals/FoodLogEditorModal/index.tsx b/src/modals/FoodLogEditorModal/index.tsx index 660e45d..bfa3877 100644 --- a/src/modals/FoodLogEditorModal/index.tsx +++ b/src/modals/FoodLogEditorModal/index.tsx @@ -139,12 +139,11 @@ export const FoodLogEditorModal = React.memo((props: Props) => { > { }} style={styles.icon} /> - {item.recognisedName} + + {item.foodDataInfo?.foodName ?? item.recognisedName} + )} /> diff --git a/src/screens/advisor/view/message/records/MessageRecords.tsx b/src/screens/advisor/view/message/records/MessageRecords.tsx index 0b17be6..2483bdd 100644 --- a/src/screens/advisor/view/message/records/MessageRecords.tsx +++ b/src/screens/advisor/view/message/records/MessageRecords.tsx @@ -33,9 +33,9 @@ export const MessageRecords = ({ onViewDiary, response, }: Props) => { - const [selected, setSelected] = useState([]); const branding = useBranding(); const { records = [] } = response; + const [selected, setSelected] = useState(records); const onFoodSelect = (result: Selection) => { const find = selected?.find((item) => item.index === result?.index); @@ -63,9 +63,11 @@ export const MessageRecords = ({ color="white" style={styles.quickSuggestionTextStyle} > - { - 'Based on the image you took, I’ve recognized the following items. Please select the items you want and log them.' - } + {response.isLogged + ? 'I’ve add the items below to your log:' + : response.recordType === 'searchTool' + ? 'Here are the items from the recipe above' + : 'Based on the image you took, I’ve recognized the following items. Please select the items you want and log them.'} { const { @@ -16,7 +15,6 @@ export const BarcodeScanScreen = () => { quickResult, resetScanning, onCreateCustomWithoutBarcodePress, - onBarcodePress, onViewExistingPress, } = useBarcodeScan(); @@ -24,17 +22,13 @@ export const BarcodeScanScreen = () => { - {quickResult && - quickResult.customFood && - quickResult.passioIDAttributes && ( - - )} + {quickResult && quickResult.customFood && ( + + )} {quickResult && quickResult.customFood == null && quickResult.passioIDAttributes && ( @@ -46,17 +40,6 @@ export const BarcodeScanScreen = () => { onViewExistingPress={onViewExistingPress} /> )} - - {quickResult && - quickResult.barcode && - quickResult.passioIDAttributes === null && ( - - )} {isLoading && } ); diff --git a/src/screens/barcode/useBarcodeScan.ts b/src/screens/barcode/useBarcodeScan.ts index ecedcd2..e86a332 100644 --- a/src/screens/barcode/useBarcodeScan.ts +++ b/src/screens/barcode/useBarcodeScan.ts @@ -59,29 +59,37 @@ export const useBarcodeScan = () => { return; } - let attribute: QuickResult | null = - await getQuickResults(barcodeCandidate); - const existingCustomFood = customFoods?.find( (i) => i.barcode === barcodeCandidate.barcode ); - if (attribute === null) { - setPassioQuickResults({ + let attribute: QuickResult | null = + await getQuickResults(barcodeCandidate); + + if (attribute?.passioIDAttributes === null) { + const result: BarcodeCustomResult = { name: barcodeCandidate.barcode, type: 'Barcode', customFood: existingCustomFood, barcode: barcodeCandidate.barcode, passioIDAttributes: null, - }); + }; + if (existingCustomFood) { + setPassioQuickResults(result); + } else { + params?.onBarcodePress?.(result); + } } else { - setPassioQuickResults({ + const result: BarcodeCustomResult = { ...attribute, + name: barcodeCandidate.barcode, + type: 'Barcode', customFood: existingCustomFood, barcode: barcodeCandidate.barcode, - type: 'Barcode', - }); + }; + setPassioQuickResults(result); } + setLoading(false); barcodeRef.current = barcodeCandidate.barcode; } @@ -91,7 +99,7 @@ export const useBarcodeScan = () => { if (detection) { init(); } - }, [customFoods, foodDetectEvents, getQuickResults]); + }, [customFoods, foodDetectEvents, getQuickResults, params]); useEffect(() => { const config: FoodDetectionConfig = { diff --git a/src/screens/editFoodLogs/editFoodLogsScreen.tsx b/src/screens/editFoodLogs/editFoodLogsScreen.tsx index 607d5da..9ce3f43 100644 --- a/src/screens/editFoodLogs/editFoodLogsScreen.tsx +++ b/src/screens/editFoodLogs/editFoodLogsScreen.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { Image, + Pressable, ScrollView, StyleSheet, TouchableOpacity, @@ -20,7 +21,7 @@ import { calculateComputedWeightAmount } from './utils'; import { useEditFoodLog } from './useEditFoodLog'; import { content } from '../../constants/Content'; import type { Branding } from '../../contexts'; -import { scaleWidth, scaled, scaledSize } from '../../utils'; +import { scaleHeight, scaleWidth, scaled, scaledSize } from '../../utils'; import { ICONS } from '../../assets'; import NewEditServingAmountView from './views/newEditServingsAmountView'; @@ -52,13 +53,15 @@ export const EditFoodLogScreen = () => { isHideTimeStamp, closeDatePicker, closeFavoriteFoodLogAlert, + onSwitchAlternativePress, closeSaveFoodNameAlert, deleteIngredient, onDateChangePress, onAddIngredientPress, onEditIngredientPress, onCancelPress, - onRightActionPress, + onDeleteFoodLogPress, + onEditCustomFoodPress, onSaveFavoriteFoodLog, onSaveFoodLogName, onSavePress, @@ -72,21 +75,48 @@ export const EditFoodLogScreen = () => { } = useEditFoodLog(); const styles = editFoodLogStyle(branding); - const icon = - from === 'MealLog' - ? ICONS.delete - : from === 'Search' - ? ICONS.editGreyIc - : from === 'QuickScan' - ? ICONS.swap - : undefined; - return ( + {from === 'MealLog' && ( + + + + )} + {from === 'QuickScan' && ( + + + + )} + + + + + } /> @@ -97,11 +127,9 @@ export const EditFoodLogScreen = () => { activeOpacity={1} > { /> value.passioID !== foodItem.passioID + (value) => value.refCode !== foodItem.refCode ), }; setFoodLog(recalculateFoodLogServing(newFoodLog)); @@ -95,12 +96,13 @@ export function useEditFoodLog() { const updateIngredient = useCallback( (foodLogObj: FoodItem) => { let updatedFoodItems = foodLog.foodItems.map((value) => - value.passioID === foodLogObj.passioID ? foodLogObj : value + value.refCode === foodLogObj.refCode ? foodLogObj : value ); let foodLogData: FoodLog = { ...foodLog, foodItems: updatedFoodItems }; setFoodLog(recalculateFoodLogServing(foodLogData)); + navigation.goBack(); }, - [foodLog, recalculateFoodLogServing] + [foodLog, navigation, recalculateFoodLogServing] ); const onUpdateFoodLog = useCallback((item: FoodLog) => { @@ -155,26 +157,26 @@ export function useEditFoodLog() { setFoodLog({ ...newFoodLog }); }; - const onRightActionPress = () => { - if (params.prevRouteName === 'QuickScan') { - navigation.navigate('FoodSearchScreen', { - from: 'Other', - onSaveData: (item: PassioFoodItem) => { - onSwitchAlternative(item); - }, - }); - } else if (params.prevRouteName === 'Search') { - const uuid: string = uuid4.v4() as string; - navigation.navigate('FoodCreatorScreen', { - foodLog: { - ...foodLog, - uuid: uuid, - }, - from: 'Search', - }); - } else { - onDeleteFoodLogPress(); - } + const onEditCustomFoodPress = () => { + const uuid: string = uuid4.v4() as string; + + navigation.push('FoodCreatorScreen', { + foodLog: { + ...foodLog, + uuid: uuid, + barcode: foodLog?.foodItems?.[0].barcode, + }, + from: 'Search', + }); + }; + + const onSwitchAlternativePress = () => { + navigation.navigate('FoodSearchScreen', { + from: 'Other', + onSaveData: (item: PassioFoodItem) => { + onSwitchAlternative(item); + }, + }); }; const onMoreDetailPress = () => { @@ -262,7 +264,10 @@ export function useEditFoodLog() { (foodItem: FoodItem) => { navigation.navigate('EditIngredientScreen', { foodItem: foodItem, - deleteIngredient, + deleteIngredient: (item) => { + deleteIngredient(item); + navigation.goBack(); + }, updateIngredient, }); }, @@ -279,12 +284,12 @@ export function useEditFoodLog() { const favoriteFoodItems = await services.dataService.getFavoriteFoodItems(); setFavorite( - favoriteFoodItems.filter((item) => item.passioID === foodLog.passioID) + favoriteFoodItems.filter((item) => item.refCode === foodLog.refCode) .length >= 1 ); } init(); - }, [foodLog.passioID, services.dataService]); + }, [foodLog.refCode, services.dataService]); return { branding, @@ -305,10 +310,12 @@ export function useEditFoodLog() { onAddIngredientPress, onCancelPress, onDateChangePress, + onDeleteFoodLogPress, onDeleteFavoritePress, + onEditCustomFoodPress, onEditIngredientPress, onMealLabelPress, - onRightActionPress, + onSwitchAlternativePress, onSaveFavoriteFoodLog, onSaveFoodLogName, onSavePress, diff --git a/src/screens/editFoodLogs/utils/EditFoodLogUtils.ts b/src/screens/editFoodLogs/utils/EditFoodLogUtils.ts index 2fe24e1..4488b7c 100644 --- a/src/screens/editFoodLogs/utils/EditFoodLogUtils.ts +++ b/src/screens/editFoodLogs/utils/EditFoodLogUtils.ts @@ -33,6 +33,18 @@ export function steps(sliderMaxValue: number): number { export function totalAmountOfNutrient( foodItems: FoodItem[], nutrientType: NutrientType +): number { + const totalAmount = totalAmountOfNutrientWithoutRound( + foodItems, + nutrientType + ); + return totalAmount > 1 + ? Math.floor(totalAmount) + : parseFloat(totalAmount.toFixed(1)); +} +export function totalAmountOfNutrientWithoutRound( + foodItems: FoodItem[], + nutrientType: NutrientType ): number { let totalAmount = 0; foodItems.forEach((foodItem) => { @@ -42,9 +54,7 @@ export function totalAmountOfNutrient( } }); }); - return totalAmount > 1 - ? Math.floor(totalAmount) - : parseFloat(totalAmount.toFixed(1)); + return totalAmount; } export function calculateMassOfServingUnit( diff --git a/src/screens/editFoodLogs/views/ingredients/IngredientItemView.tsx b/src/screens/editFoodLogs/views/ingredients/IngredientItemView.tsx index 4d2c0f5..4a9e9e3 100644 --- a/src/screens/editFoodLogs/views/ingredients/IngredientItemView.tsx +++ b/src/screens/editFoodLogs/views/ingredients/IngredientItemView.tsx @@ -97,8 +97,7 @@ const IngredientView = (props: Props) => { > diff --git a/src/screens/editFoodLogs/views/logInformationsView.tsx b/src/screens/editFoodLogs/views/logInformationsView.tsx index e609597..26e4d17 100644 --- a/src/screens/editFoodLogs/views/logInformationsView.tsx +++ b/src/screens/editFoodLogs/views/logInformationsView.tsx @@ -4,21 +4,19 @@ import { StyleSheet, TouchableOpacity, View } from 'react-native'; import type { FoodItem, PassioIconType } from '../../../models'; import { PassioFoodIcon } from '../../../components/passio/PassioFoodIcon'; -import type { PassioID } from '@passiolife/nutritionai-react-native-sdk-v3/src/sdk/v2'; import { content } from '../../../constants/Content'; -import { totalAmountOfNutrient } from '../utils'; +import { totalAmountOfNutrientWithoutRound } from '../utils'; import { scaleHeight, scaleWidth } from '../../../utils'; import DoughnutChart from '../../../components/doughnutChart/DoughnutChart'; import { useBranding } from '../../../contexts'; +import { macroNutrientPercentages } from '../../../utils/V3Utils'; interface Props { foodItems: FoodItem[]; - passioID: PassioID; isOpenFood?: boolean; - entityType: PassioIconType; + entityType?: PassioIconType; name: string; - imageName: string; - userFoodImage?: string; + iconID?: string; qty: number; servingUnit: string; longName?: string; @@ -31,31 +29,27 @@ const LogInformationView = ({ foodItems, name, isOpenFood, - qty, - servingUnit, - weight, - userFoodImage, + iconID, entityType, onMoreDetailPress, - imageName, rightIconForHeader, longName, }: Props) => { - const calories = totalAmountOfNutrient(foodItems, 'calories'); - const carbs = totalAmountOfNutrient(foodItems, 'carbs'); - const protein = totalAmountOfNutrient(foodItems, 'protein'); - const fat = totalAmountOfNutrient(foodItems, 'fat'); + const calories = totalAmountOfNutrientWithoutRound(foodItems, 'calories'); + const carbs = totalAmountOfNutrientWithoutRound(foodItems, 'carbs'); + const protein = totalAmountOfNutrientWithoutRound(foodItems, 'protein'); + const fat = totalAmountOfNutrientWithoutRound(foodItems, 'fat'); const branding = useBranding(); + const { carbsPercentage, fatPercentage, proteinPercentage } = + macroNutrientPercentages(carbs, fat, protein); return ( @@ -68,14 +62,16 @@ const LogInformationView = ({ > {name} - - {longName ? longName : `${qty} ${servingUnit} (${weight}${'g'})`} - + {longName && ( + + {longName} + + )} {rightIconForHeader ? ( @@ -88,15 +84,15 @@ const LogInformationView = ({ - {calories} + {Math.round(calories)} - {carbs} g + {carbs.toFixed(1)} g - (30%) + {`(${carbsPercentage.toFixed(1)}%)`} @@ -167,7 +163,7 @@ const LogInformationView = ({ testID="testNutrientProtein" style={styles.otherNutrientTValue} > - {protein} g + {protein.toFixed(1)} g - (30%) + {`(${proteinPercentage.toFixed(1)}%)`} @@ -195,7 +191,7 @@ const LogInformationView = ({ testID="testNutrientFat" style={styles.otherNutrientTValue} > - {fat} g + {fat.toFixed(1)} g - (30%) + {`(${fatPercentage.toFixed(1)}%)`} diff --git a/src/screens/editIngredients/EditIngredientScreen.tsx b/src/screens/editIngredients/EditIngredientScreen.tsx index 16e088f..303f55a 100644 --- a/src/screens/editIngredients/EditIngredientScreen.tsx +++ b/src/screens/editIngredients/EditIngredientScreen.tsx @@ -6,19 +6,10 @@ import { calculateComputedWeightAmount, DeleteIngredientAlert, } from '../editFoodLogs'; -import { - BasicButton, - DeleteButton, - AlternativeFoodLogsView, - BackNavigation, -} from '../../components'; +import { BasicButton, DeleteButton, BackNavigation } from '../../components'; import { COLORS } from '../../constants'; import LogInformationView from '../editFoodLogs/views/logInformationsView'; -import { - type RouteProp, - useNavigation, - useRoute, -} from '@react-navigation/native'; +import { type RouteProp, useRoute } from '@react-navigation/native'; import type { StackNavigationProp } from '@react-navigation/stack'; import EditServingAmountView from '../editFoodLogs/views/EditServingAmountView'; import { useEditIngredient } from './useEditIngredient'; @@ -40,11 +31,8 @@ export const EditIngredientScreen = () => { export const EditIngredient = (props?: EditIngredientsScreenProps) => { const { params } = useRoute>(); - const navigation = useNavigation(); - const { foodItem, updateFoodItem, onSwitchAlternative } = useEditIngredient( - params ?? props - ); + const { foodItem, updateFoodItem } = useEditIngredient(params ?? props); const onSavePress = async () => { await saveIngredient().then(() => {}); @@ -62,7 +50,6 @@ export const EditIngredient = (props?: EditIngredientsScreenProps) => { async onDelete() { if (params.deleteIngredient !== undefined && foodItem) { params.deleteIngredient(foodItem); - navigation.goBack(); } }, }); @@ -77,10 +64,9 @@ export const EditIngredient = (props?: EditIngredientsScreenProps) => { { } }} /> - - onSwitchAlternative(passioIDAttributes) - } - /> diff --git a/src/screens/editRecipe/useEditFoodLog.ts b/src/screens/editRecipe/useEditFoodLog.ts index bae6f7b..0e9ed3f 100644 --- a/src/screens/editRecipe/useEditFoodLog.ts +++ b/src/screens/editRecipe/useEditFoodLog.ts @@ -60,8 +60,9 @@ export function useEditRecipe() { updatedFoodLog.foodItems.push(foodItem); setFoodLog(recalculateFoodLogServing(updatedFoodLog)); ShowToast('Ingredient added successfully'); + navigation.goBack(); }, - [foodLog, recalculateFoodLogServing] + [foodLog, navigation, recalculateFoodLogServing] ); const deleteIngredient = useCallback( @@ -69,7 +70,7 @@ export function useEditRecipe() { const newFoodLog = { ...foodLog, foodItems: foodLog.foodItems.filter( - (value) => value.passioID !== foodItem.passioID + (value) => value.refCode !== foodItem.refCode ), }; setFoodLog(recalculateFoodLogServing(newFoodLog)); @@ -80,12 +81,13 @@ export function useEditRecipe() { const updateIngredient = useCallback( (foodLogObj: FoodItem) => { let updatedFoodItems = foodLog.foodItems.map((value) => - value.passioID === foodLogObj.passioID ? foodLogObj : value + value.refCode === foodLogObj.refCode ? foodLogObj : value ); let foodLogData: FoodLog = { ...foodLog, foodItems: updatedFoodItems }; setFoodLog(recalculateFoodLogServing(foodLogData)); + navigation.goBack(); }, - [foodLog, recalculateFoodLogServing] + [foodLog, navigation, recalculateFoodLogServing] ); const onUpdateFoodLog = useCallback((item: FoodLog) => { @@ -104,7 +106,7 @@ export function useEditRecipe() { }; const onAddIngredientPress = () => { - navigation.push('FoodSearchScreen', { + navigation.navigate('FoodSearchScreen', { onSaveData: (item) => { const foodItem = convertPassioFoodItemToFoodLog( item, @@ -123,7 +125,10 @@ export function useEditRecipe() { (foodItem: FoodItem) => { navigation.navigate('EditIngredientScreen', { foodItem: foodItem, - deleteIngredient, + deleteIngredient: (food) => { + deleteIngredient(food); + navigation.goBack(); + }, updateIngredient, }); }, diff --git a/src/screens/foodCreator/FoodCreator.utils.ts b/src/screens/foodCreator/FoodCreator.utils.ts index aa04c63..c4fd328 100644 --- a/src/screens/foodCreator/FoodCreator.utils.ts +++ b/src/screens/foodCreator/FoodCreator.utils.ts @@ -8,9 +8,19 @@ import { import uuid4 from 'react-native-uuid'; import type { FoodCreatorFoodDetailType } from './views/FoodCreatorFoodDetail'; import type { RequireNutritionFactsType } from './views/RequireNutritionFacts'; -import type { PassioFoodItem } from '@passiolife/nutritionai-react-native-sdk-v3'; +import type { + NutritionFacts, + PassioFoodItem, +} from '@passiolife/nutritionai-react-native-sdk-v3'; import { convertPassioFoodItemToFoodLog } from '../../utils/V3Utils'; +export const CUSTOM_USER_FOOD = 'user-food-'; + +export const generateCustomID = () => { + const uuid: string = uuid4.v4() as string; + return CUSTOM_USER_FOOD + uuid; +}; + export interface createFoodLogUsingFoodCreator { info: Record; requireNutritionFact: Record; @@ -90,12 +100,12 @@ export const createFoodLogUsingFoodCreator = ({ unit: unit, value: factWeight, }, - passioID: '', name: info?.name!, barcode: info?.barcode, - imageName: '', + iconId: image, entityType: 'user-food', nutrients: nutrients, + refCode: '', selectedUnit: factUnits!, selectedQuantity: Number(requireNutritionFact?.ServingSize!), servingSizes: [ @@ -127,6 +137,7 @@ export const createFoodLogUsingFoodCreator = ({ barcode: info.barcode, brandName: info.brand, userFoodImage: image, + iconID: image, uuid: uuid, }; @@ -167,3 +178,119 @@ export const isValidDecimalNumber = (text?: string, isCharacter?: boolean) => { return false; } }; +export const createCustomFoodUsingNutritionFact = ( + facts: NutritionFacts, + barcode?: string +) => { + let nutrients: Nutrient[] = []; + + if (facts.calories) { + nutrients.push({ + id: 'calories', + amount: facts.calories, + unit: nutrientUnits.calories, + }); + } + + if (facts.fat) { + nutrients.push({ + id: 'fat', + amount: facts.fat, + unit: nutrientUnits.fat, + }); + } + + if (facts.carbs) { + nutrients.push({ + id: 'carbs', + amount: facts.carbs, + unit: nutrientUnits.carbs, + }); + } + + if (facts.protein) { + nutrients.push({ + id: 'protein', + amount: facts.protein, + unit: nutrientUnits.protein, + }); + } + + if (facts.saturatedFat) { + nutrients.push({ + id: 'satFat', + amount: facts.saturatedFat, + unit: nutrientUnits.satFat, + }); + } + + if (facts.transFat) { + nutrients.push({ + id: 'transFat', + amount: facts.transFat, + unit: nutrientUnits.fat, + }); + } + + if (facts.cholesterol) { + nutrients.push({ + id: 'cholesterol', + amount: facts.cholesterol, + unit: nutrientUnits.cholesterol, + }); + } + + if (facts.sugars) { + nutrients.push({ + id: 'sugars', + amount: facts.sugars, + unit: nutrientUnits.sugars, + }); + } + + if (facts.sugarAlcohol) { + nutrients.push({ + id: 'sugarAlcohol', + amount: facts.sugarAlcohol, + unit: nutrientUnits.sugarAlcohol, + }); + } + + if (facts.dietaryFiber) { + nutrients.push({ + id: 'fiber', + amount: facts.dietaryFiber, + unit: nutrientUnits.fiber, + }); + } + + if (facts.sodium) { + nutrients.push({ + id: 'sodium', + amount: facts.sodium, + unit: nutrientUnits.sodium, + }); + } + + const foodItems: FoodItem = { + nutrients: nutrients, + name: '', + entityType: 'user-food', + computedWeight: { + unit: facts.servingSizeUnit ?? 'g', + value: facts.servingSizeGram ?? 0, + }, + selectedUnit: facts.servingSizeUnitName ?? '', + selectedQuantity: facts.servingSizeQuantity ?? 0, + refCode: '', + servingSizes: [], + servingUnits: [], + }; + const customFood: CustomFood = { + barcode: barcode, + uuid: '', + foodItems: [foodItems], + ...foodItems, + }; + return customFood; +}; diff --git a/src/screens/foodCreator/FoodCreatorScreen.tsx b/src/screens/foodCreator/FoodCreatorScreen.tsx index 4bb012b..2db4d53 100644 --- a/src/screens/foodCreator/FoodCreatorScreen.tsx +++ b/src/screens/foodCreator/FoodCreatorScreen.tsx @@ -20,6 +20,7 @@ export const FoodCreatorScreen = () => { image, isImagePickerVisible, onSavePress, + onCancelPress, onBarcodePress, onEditImagePress, onSelectImagePress, @@ -61,6 +62,7 @@ export const FoodCreatorScreen = () => { > { params.foodLog ); - const [image, setImage] = useState( - foodLog?.userFoodImage + const [image, setImage] = useState( + foodLog?.iconID + ? { + id: foodLog?.iconID, + base64: '', + } + : undefined ); const [isImagePickerVisible, setImagePickerModalVisible] = useState(false); @@ -50,12 +58,16 @@ export const useFoodCreator = () => { const onBarcodePress = async () => { navigation.navigate('BarcodeScanScreen', { onViewExistingItem: (item) => { - navigation.goBack(); if (item?.customFood) { // If the user clicks on the "View Food Item", they're navigated to the food details screen of that custom food. // Might be in this case they navigate to the new create food detail screen. - setCustomFood(item?.customFood); + navigation.push('FoodCreatorScreen', { + from: 'MyFood', + foodLog: item.customFood, + }); + // setCustomFood(item?.customFood); } else { + navigation.goBack(); // custom food doesn't exist // . If the user clicks on the "View Food Item", they're navigated to the food details screen of that food item if (item?.passioIDAttributes) { @@ -64,9 +76,9 @@ export const useFoodCreator = () => { undefined, undefined ); - navigation.navigate('EditFoodLogScreen', { + navigation.push('EditFoodLogScreen', { foodLog: barcodeFoodLog, - prevRouteName: 'Other', + prevRouteName: 'MyFood', }); } } @@ -85,12 +97,11 @@ export const useFoodCreator = () => { navigation.goBack(); if (item?.customFood) { //If they click on "Create Custom Food Without Barcode", the barcode value is left as empty in the Food Creator screen. - if (item?.passioIDAttributes) { - const customFood = convertPassioFoodItemToCustomFood( - item.passioIDAttributes - ); - setCustomFood(customFood); - } + setCustomFood({ + ...item?.customFood, + barcode: undefined, + uuid: uuid4.v4() as string, + }); } else { // custom food doesn't exist // If they click on "Create Custom Food Anyway", the barcode value is imported into the Food Creator screen. @@ -130,7 +141,7 @@ export const useFoodCreator = () => { info: info?.records, requireNutritionFact: requireNutritionFact?.records, otherNutritionFact: otherNutritionFact?.records, - image, + image: image?.id, }); try { @@ -165,13 +176,28 @@ export const useFoodCreator = () => { if (uris) { const uri = Platform.OS === 'android' ? `file://${uris[0]}` : uris[0]; const response = await RNFS.readFile(uri, 'base64'); - setImage(response); + let id = generateCustomID(); + if (image?.id.includes(CUSTOM_USER_FOOD)) { + id = image?.id; + } + let customFoodImageID = await services.dataService.saveImage({ + id: id, + base64: response, + }); + setImage({ + id: customFoodImageID, + base64: response, + }); navigation.goBack(); } }, }); }; + const onCancelPress = () => { + navigation.goBack(); + }; + return { branding, foodLog, @@ -185,6 +211,7 @@ export const useFoodCreator = () => { closeImagePickerModal, onSavePress, onBarcodePress, + onCancelPress, onEditImagePress, }; }; diff --git a/src/screens/foodCreator/views/FoodCreatorFoodDetail.tsx b/src/screens/foodCreator/views/FoodCreatorFoodDetail.tsx index 1f74b0e..d14da57 100644 --- a/src/screens/foodCreator/views/FoodCreatorFoodDetail.tsx +++ b/src/screens/foodCreator/views/FoodCreatorFoodDetail.tsx @@ -6,20 +6,19 @@ import React, { useState, } from 'react'; import { Card, Text } from '../../../components'; -import { Image, StyleSheet, TouchableOpacity, View } from 'react-native'; +import { StyleSheet, TouchableOpacity, View } from 'react-native'; import { Branding, useBranding } from '../../../contexts'; import { FiledView, FiledViewRef } from '../../../components/filed/FiledView'; import type { FiledSelectionViewRef } from '../../../components/filed/FiledSelectionView'; -import { ICONS } from '../../../assets'; import { FiledViewClick } from '../../../components/filed/FiledViewClick'; -import type { CustomFood } from '../../../models'; +import type { CustomFood, Image } from '../../../models'; import { PassioFoodIcon } from '../../../components/passio/PassioFoodIcon'; interface Props { foodLog?: CustomFood; onBarcodePress?: () => void; onEditImagePress?: () => void; - image?: string; + image?: Image; } export type FoodCreatorFoodDetailType = 'name' | 'brand' | 'barcode'; @@ -105,22 +104,11 @@ export const FoodCreatorFoodDetail = React.forwardRef< {'Scan Description'} - {image || foodLog?.iconID ? ( - - ) : ( - - )} + Value; } -interface DefaultNutrients { +export interface DefaultNutrients { label: NutrientType; value?: number; } @@ -39,16 +40,18 @@ export const OtherNutritionFacts = React.forwardRef< useEffect(() => { // Generate default nutrients where come from `foodLog - const defaultKey: DefaultNutrients[] | undefined = - foodLog?.foodItems?.[0]?.nutrients - .filter((i) => i.amount > 0 && OtherNutrients.includes(i.id)) - .map((i) => { - const data: DefaultNutrients = { - label: i.id as NutrientType, - value: Number(i.amount.toFixed(2)), - }; - return data; - }); + const allNutrients: DefaultNutrients[] | undefined = foodLog?.foodItems + ?.flatMap((o) => o.nutrients) + .filter((i) => i.amount > 0 && OtherNutrients.includes(i.id)) + .map((i) => { + const data: DefaultNutrients = { + label: i.id as NutrientType, + value: Number(i.amount.toFixed(2)), + }; + return data; + }); + + const defaultKey = sumNutrients(allNutrients ?? []); setDefaultList( OtherNutrients.filter((i) => !defaultKey?.find((l) => l.label === i)) ?? diff --git a/src/screens/foodCreator/views/RequireNutritionFacts.tsx b/src/screens/foodCreator/views/RequireNutritionFacts.tsx index c82c63d..160798e 100644 --- a/src/screens/foodCreator/views/RequireNutritionFacts.tsx +++ b/src/screens/foodCreator/views/RequireNutritionFacts.tsx @@ -20,6 +20,7 @@ import { WEIGHT_UNIT_SPLIT_IDENTIFIER, } from '../FoodCreator.utils'; import type { CustomFood } from '../../../models'; +import { totalAmountOfNutrient } from '../../../screens/editFoodLogs'; interface Props { foodLog?: CustomFood; @@ -138,15 +139,10 @@ export const RequireNutritionFacts = React.forwardRef< let carbs; let proteins; - foodLog?.foodItems?.[0].nutrients.forEach((i) => { - if (i.amount) { - const amount = i.amount.toFixed(2).toString(); - if (i.id === 'calories') calories = amount; - if (i.id === 'carbs') carbs = amount; - if (i.id === 'protein') proteins = amount; - if (i.id === 'fat') fat = amount; - } - }); + calories = totalAmountOfNutrient(foodLog?.foodItems ?? [], 'calories'); + carbs = totalAmountOfNutrient(foodLog?.foodItems ?? [], 'carbs'); + proteins = totalAmountOfNutrient(foodLog?.foodItems ?? [], 'protein'); + fat = totalAmountOfNutrient(foodLog?.foodItems ?? [], 'fat'); return ( @@ -172,8 +168,15 @@ export const RequireNutritionFacts = React.forwardRef< @@ -181,24 +184,24 @@ export const RequireNutritionFacts = React.forwardRef< diff --git a/src/screens/meallogss/useMealLogs.ts b/src/screens/meallogss/useMealLogs.ts index 8185478..30ccaaf 100644 --- a/src/screens/meallogss/useMealLogs.ts +++ b/src/screens/meallogss/useMealLogs.ts @@ -10,7 +10,7 @@ import { useServices } from '../../contexts'; import type { FoodLog } from '../../models'; import { getMealLogsForDate } from '../../utils/DataServiceHelper'; import { AsyncStorageHelper } from '../../utils/AsyncStorageHelper'; -import type { ParamList } from '../../navigaitons'; +import type { Module, ParamList } from '../../navigaitons'; import { ShowToast, getMealLog } from '../../utils'; import type BottomSheet from '@gorhom/bottom-sheet'; import type { QuickSuggestion } from '../../models/QuickSuggestion'; @@ -94,7 +94,7 @@ export function useMealLogs() { }; if (isOpenEditor) { - navigateToEditFoodLog(updateFoodLog); + navigateToEditFoodLog(updateFoodLog, 'Other'); } else { await services.dataService.saveFoodLog(updateFoodLog); ShowToast(`"${updateFoodLog.name}" added into "${updateFoodLog.meal}"`); @@ -110,10 +110,10 @@ export function useMealLogs() { }; const navigateToEditFoodLog = useCallback( - (foodLog: FoodLog) => { + (foodLog: FoodLog, prevRouteName?: Module) => { navigation.navigate('EditFoodLogScreen', { foodLog: foodLog, - prevRouteName: 'MealLog', + prevRouteName: prevRouteName ?? 'MealLog', }); }, [navigation] diff --git a/src/screens/meallogss/views/MealLogItemView.tsx b/src/screens/meallogss/views/MealLogItemView.tsx index 0e28444..b3ad3f1 100644 --- a/src/screens/meallogss/views/MealLogItemView.tsx +++ b/src/screens/meallogss/views/MealLogItemView.tsx @@ -30,11 +30,9 @@ const MealLogItemView = (props: Props) => { diff --git a/src/screens/myFavoritess/views/FavoriteFoodLogView.tsx b/src/screens/myFavoritess/views/FavoriteFoodLogView.tsx index 17619d3..fe5fad4 100644 --- a/src/screens/myFavoritess/views/FavoriteFoodLogView.tsx +++ b/src/screens/myFavoritess/views/FavoriteFoodLogView.tsx @@ -22,8 +22,7 @@ const FavoriteFoodLogView = (props: Props) => { diff --git a/src/screens/myFoods/useMyFoodScreen.ts b/src/screens/myFoods/useMyFoodScreen.ts index abeae00..34c5d1c 100644 --- a/src/screens/myFoods/useMyFoodScreen.ts +++ b/src/screens/myFoods/useMyFoodScreen.ts @@ -5,7 +5,7 @@ import type { StackNavigationProp } from '@react-navigation/stack'; import type { ParamList } from '../../navigaitons'; import { useIsFocused, useNavigation } from '@react-navigation/native'; import uuid4 from 'react-native-uuid'; -import { getMealLog } from '../../utils'; +import { getMealLog, ShowToast } from '../../utils'; import { convertDateToDBFormat } from '../../utils/DateFormatter'; import { convertPassioFoodItemToFoodLog } from '../../utils/V3Utils'; @@ -70,23 +70,19 @@ export const useMyFoodScreen = () => { }); }; - const onLogPress = (food: CustomFood) => { + const onLogPress = async (food: CustomFood) => { const date = new Date(); const meal = getMealLog(date, undefined); const uuid: string = uuid4.v4() as string; const foodLog: FoodLog = { ...food, - passioID: '', eventTimestamp: convertDateToDBFormat(date), meal: meal, uuid: uuid, }; - - navigation.navigate('EditFoodLogScreen', { - foodLog: foodLog, - prevRouteName: 'Other', - }); + await services.dataService.saveFoodLog(foodLog); + ShowToast('Added your food into ' + meal); }; return { branding, diff --git a/src/screens/myFoods/views/customFoods/CustomFoods.tsx b/src/screens/myFoods/views/customFoods/CustomFoods.tsx index 0509a51..c3739cf 100644 --- a/src/screens/myFoods/views/customFoods/CustomFoods.tsx +++ b/src/screens/myFoods/views/customFoods/CustomFoods.tsx @@ -34,11 +34,10 @@ const CustomFoods = ({ const renderCustomFood = ({ item }: { item: CustomFood }) => { return ( onPressLog?.(item)} onPressEditor={() => onPressEditor?.(item)} onPressDelete={() => onDeleteCustomFood?.(item)} diff --git a/src/screens/myFoods/views/customFoods/views/CustomFoodsItem.tsx b/src/screens/myFoods/views/customFoods/views/CustomFoodsItem.tsx index 4a76084..a5b4e46 100644 --- a/src/screens/myFoods/views/customFoods/views/CustomFoodsItem.tsx +++ b/src/screens/myFoods/views/customFoods/views/CustomFoodsItem.tsx @@ -12,10 +12,9 @@ import { scaled } from '../../../../../utils'; interface Props { passioID: PassioID; - imageName: string; + iconID: string; name: string; brandName?: string; - userImage?: string; onPressEditor: () => void; onPressDelete: () => void; onPressLog: () => void; @@ -23,15 +22,13 @@ interface Props { } const CustomFoodsItem = ({ - passioID, name, onPressEditor, onPressLog, onPressDelete, entityType, - userImage, + iconID, brandName, - imageName, }: Props) => { return ( @@ -43,11 +40,9 @@ const CustomFoodsItem = ({ diff --git a/src/screens/myPlans/mealPlan/useMealPlan.ts b/src/screens/myPlans/mealPlan/useMealPlan.ts index 3c19bc9..a8d5900 100644 --- a/src/screens/myPlans/mealPlan/useMealPlan.ts +++ b/src/screens/myPlans/mealPlan/useMealPlan.ts @@ -14,8 +14,7 @@ import { type PassioMealPlanItem, } from '@passiolife/nutritionai-react-native-sdk-v3'; import type { ParamList } from '../../../navigaitons'; -import { convertPassioFoodItemToFoodLog } from '../../../utils/V3Utils'; -import { ShowToast } from '../../../utils'; +import { createFoodLogUsingPortionSize, ShowToast } from '../../../utils'; export function useMealPlan() { const services = useServices(); @@ -44,22 +43,20 @@ export function useMealPlan() { const convertFoodLog = async (item: PassioMealPlanItem) => { let result = await PassioSDK.fetchFoodItemForDataInfo(item.meal); + let qty = item.meal?.nutritionPreview?.servingQuantity ?? '1'; + let servingUnit = item.meal?.nutritionPreview?.servingUnit ?? ''; + let weightGram = item.meal?.nutritionPreview?.weightQuantity ?? 0; + const portionSize = `${qty} ${servingUnit}`; + if (result) { - if (item.meal) { - result.amount.weight = { - unit: item.meal.nutritionPreview?.weightUnit ?? 'gram', - value: item.meal.nutritionPreview?.weightQuantity ?? 0, - }; - result.amount.selectedUnit = - item.meal.nutritionPreview?.servingUnit ?? 'gram'; - result.amount.selectedQuantity = - item.meal.nutritionPreview?.servingQuantity ?? 0; - } - const log = convertPassioFoodItemToFoodLog( + let log = createFoodLogUsingPortionSize( result, - params.logToDate, - item.mealTime.toLowerCase() as MealLabel + params.logToDate ?? new Date(), + item.mealTime.toLowerCase() as MealLabel, + weightGram, + portionSize ); + return log; } else { return null; diff --git a/src/screens/quick/QuickScanningScreen.tsx b/src/screens/quick/QuickScanningScreen.tsx index 5654644..57c7eb7 100644 --- a/src/screens/quick/QuickScanningScreen.tsx +++ b/src/screens/quick/QuickScanningScreen.tsx @@ -1,35 +1,22 @@ -import React, { useMemo } from 'react'; -import { - Dimensions, - FlatList, - Platform, - StyleSheet, - TouchableOpacity, - View, -} from 'react-native'; +import React, { useState } from 'react'; +import { Image, StyleSheet, TouchableOpacity, View } from 'react-native'; import { DetectionCameraView } from '@passiolife/nutritionai-react-native-sdk-v3/src/sdk/v2'; import type { MealLabel } from '../../models'; -import { - QuickScanningActionView, - QuickScanningLoadingView, - QuickScanningResultView, -} from './views'; +import { QuickScanningActionView } from './views'; import type { StackNavigationProp } from '@react-navigation/stack'; import type { ParamList } from '../../navigaitons'; -import { useQuickScan } from './useQuickScan'; -import NutritionFactView from './views/NutritionFactView'; import { gestureHandlerRootHOC } from 'react-native-gesture-handler'; -import BottomSheet, { BottomSheetView } from '@gorhom/bottom-sheet'; -import { scaleHeight } from '../../utils'; -import { Text } from '../../components'; import ScanSVG from '../../components/svgs/scan'; -import { useBranding } from '../../contexts'; -import AlternateFoodLogView from '../../components/alternatives/alternativesItem'; -import { QuickScanItemAddedToDiaryView } from './views/QuickSacnItemAddedToDiaryView'; -import { QuickScanLogButtonView } from './views/QuickScanLogButtonView'; import QuickScanInfo from './views/QuickScanInfo'; +import { useNavigation } from '@react-navigation/native'; +import { BarcodeFoodScan } from './mode/barcode/BarcodeFoodScan'; +import { ICONS } from '../../assets'; +import { VisualFoodScan } from './mode/visual/VisualFoodScan'; +import { useBranding } from '../../contexts'; +import { NutritionFactScan } from './mode/nutritionFact/NutritionFactScan'; +import { ShowToast } from '../../utils'; export interface ScanningScreenProps { logToDate: Date | undefined; @@ -41,134 +28,117 @@ export type ScanningScreenNavigationProps = StackNavigationProp< 'ScanningScreen' >; -export const QuickScanningScreen = gestureHandlerRootHOC(() => { - const { - alternatives, - info, - isLodgedFood, - isStopScan, - nutritionFacts, - passioQuickResults, - onClosed, - onContinueScanningPress, - onFoodSearchManuallyPress, - onLogFoodPress, - onOpenFoodLogEditor, - onSaveFoodLogUsingNutrientFact, - onUpdatingNutritionFacFlag, - onViewDiaryPress, - resetScanning, - setInfo, - setStopScan, - } = useQuickScan(); +export type ScanningMode = 'Visual' | 'Barcode' | 'NutritionFact'; +export const QuickScanningScreen = gestureHandlerRootHOC(() => { + const [info, setInfo] = useState(false); + const [mode, setMode] = useState('Visual'); + const navigation = useNavigation(); const branding = useBranding(); - const snapPoints = useMemo( - () => [scaleHeight(Platform.OS === 'android' ? 260 : 240)], - [] - ); + const renderMode = () => { + return ( + + { + setMode('Visual'); + ShowToast('Whole Food Mode'); + }} + > + + + { + setMode('Barcode'); + ShowToast('Barcode Mode'); + }} + > + + + { + ShowToast('Nutrition Facts Mode'); + setMode('NutritionFact'); + }} + style={[ + styles.iconsContainer, + mode === 'NutritionFact' && styles.iconsContainerSelected, + ]} + > + + + + ); + }; return ( - + {!info && ( )} - {!isLodgedFood && !info && ( - {}} - index={isLodgedFood ? -1 : 0} - enableDynamicSizing - onChange={(index) => { - if (alternatives && alternatives?.length > 0) { - setStopScan(index === 1); - } - }} - snapPoints={snapPoints} - handleIndicatorStyle={{ - backgroundColor: nutritionFacts ? 'white' : branding.border, - }} - backgroundStyle={styles.bottomSheetChildrenContainer} - > - {passioQuickResults === null && nutritionFacts === null ? ( - - - - - ) : null} - - {nutritionFacts !== null ? ( - resetScanning()} - onPreventToUpdatingNutritionFact={onUpdatingNutritionFacFlag} - onNext={onSaveFoodLogUsingNutrientFact} - /> - ) : ( - <> - {passioQuickResults !== undefined && - passioQuickResults !== null ? ( - - - {!isStopScan && alternatives && alternatives?.length > 0 - ? ' Pull tray up to see more options. ' - : ''} - - - ( - onOpenFoodLogEditor(item)} - > - - - )} - /> - - - ) : null} - - )} - - )} - - {isLodgedFood === false && - !info && - nutritionFacts === null && - passioQuickResults ? ( - onOpenFoodLogEditor(passioQuickResults)} - onSaveFoodLog={() => onLogFoodPress(passioQuickResults)} - onFoodSearchManuallyPress={onFoodSearchManuallyPress} - /> - ) : null} - - {isLodgedFood && ( - - )} + + {mode === 'Visual' && !info && } + {mode === 'Barcode' && !info && ( + { + setMode('NutritionFact'); + }} + /> + )} + {mode === 'NutritionFact' && !info && } + navigation.goBack()} onInfoPress={() => { setInfo((i) => !i); }} /> {info && setInfo(false)} />} + {renderMode()} ); }); @@ -190,6 +160,19 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', }, + icons: { + height: 24, + width: 24, + }, + iconsContainer: { + backgroundColor: 'rgba(255, 255, 255, 0.4)', + marginHorizontal: 8, + borderRadius: 32, + padding: 6, + }, + iconsContainerSelected: { + backgroundColor: 'rgba(79, 70, 229, 1)', + }, bottomSheetChildrenContainer: { flex: 1, }, diff --git a/src/screens/quick/mock/mockResponse.ts b/src/screens/quick/mock/mockResponse.ts deleted file mode 100644 index e84cbeb..0000000 --- a/src/screens/quick/mock/mockResponse.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { - PassioIDEntityType, - type PassioIDAttributes, -} from '@passiolife/nutritionai-react-native-sdk-v3/src/sdk/v2'; - -export const mockPassioIDAttributes: PassioIDAttributes = { - passioID: '90f84c1e-9a0d-11ea-98a4-b3eebae4841d', - name: 'apple', - imageName: 'VEG0002', - entityType: PassioIDEntityType.item, - parents: [], - children: [], - siblings: [], - isOpenFood: false, - foodItem: { - passioID: '90f84c1e-9a0d-11ea-98a4-b3eebae4841d', - name: 'apple', - imageName: 'VEG0002', - selectedQuantity: 1, - selectedUnit: 'medium (3" dia)', - entityType: PassioIDEntityType.item, - servingUnits: [ - { value: 182, unit: 'g', unitName: 'medium (3" dia)' }, - { value: 125, unit: 'g', unitName: 'cup, quartered or chopped' }, - { value: 110, unit: 'g', unitName: 'cup, sliced' }, - { value: 26, unit: 'g', unitName: 'oz, with skin, yields' }, - { value: 35, unit: 'g', unitName: 'crabapple' }, - { value: 125, unit: 'g', unitName: 'cup' }, - { value: 14, unit: 'g', unitName: 'ring' }, - { value: 17, unit: 'g', unitName: 'slice' }, - { value: 7, unit: 'g', unitName: 'thin slice' }, - { value: 149, unit: 'g', unitName: 'small (2-3/4" dia)' }, - { value: 223, unit: 'g', unitName: 'large (3-1/4" dia)' }, - { value: 2.6041666666666665, unit: 'g', unitName: 'tsp' }, - { value: 7.8125, unit: 'g', unitName: 'tbsp' }, - { value: 1, unit: 'g', unitName: 'gram' }, - ], - servingSizes: [ - { unitName: 'medium (3" dia)', quantity: 1 }, - { unitName: 'cup, quartered or chopped', quantity: 1 }, - { unitName: 'cup, sliced', quantity: 1 }, - { unitName: 'oz, with skin, yields', quantity: 1 }, - { unitName: 'crabapple', quantity: 1 }, - { unitName: 'cup', quantity: 1 }, - { unitName: 'ring', quantity: 1 }, - { unitName: 'slice', quantity: 1 }, - { unitName: 'thin slice', quantity: 1 }, - { unitName: 'small (2-3/4" dia)', quantity: 1 }, - { unitName: 'large (3-1/4" dia)', quantity: 1 }, - { unitName: 'gram', quantity: 100 }, - ], - computedWeight: { value: 182, unit: 'g' }, - vitaminA: { unit: 'IU', value: 0 }, - alcohol: { unit: 'g', value: 0 }, - calcium: { unit: 'mg', value: 10.92 }, - calories: { unit: 'kcal', value: 94.64 }, - carbs: { unit: 'g', value: 25.134200000000003 }, - cholesterol: { unit: 'mg', value: 0 }, - fat: { unit: 'g', value: 0.3094 }, - iodine: { unit: 'ug', value: 0 }, - iron: { unit: 'mg', value: 0.2184 }, - magnesium: { unitName: 'mg', value: 9.1, unit: 'mg' }, - monounsaturatedFat: { - unitName: 'g', - value: 0.012740000000000001, - unit: 'mg', - }, - phosphorus: { unit: 'mg', value: 20.02 }, - polyunsaturatedFat: { unit: 'g', value: 0.09282 }, - potassium: { unit: 'mg', value: 194.74 }, - protein: { unit: 'g', value: 0.4732 }, - sodium: { unit: 'mg', value: 1.82 }, - sugarAlcohol: { unit: 'g', value: 0 }, - transFat: { unit: 'g', value: 0 }, - vitaminB12: { unit: 'ug', value: 0 }, - vitaminB12Added: { unit: 'ug', value: 0 }, - vitaminB6: { unit: 'mg', value: 0.07462 }, - vitaminC: { unit: 'mg', value: 8.372 }, - vitaminD: { unit: 'ug', value: 0 }, - vitaminE: { unit: 'mg', value: 0.3276 }, - vitaminEAdded: { unit: 'mg', value: 0 }, - ingredientsDescription: 'Apple, raw', - }, - recipe: { - passioID: '90f84c1e-9a0d-11ea-98a4-b3eebae4841d', - name: 'apple', - imageName: 'VEG0002', - selectedQuantity: 1, - selectedUnit: 'medium (3" dia)', - servingUnits: [ - { value: 182, unit: 'g', unitName: 'medium (3" dia)' }, - { value: 125, unit: 'g', unitName: 'cup, quartered or chopped' }, - { value: 110, unit: 'g', unitName: 'cup, sliced' }, - { value: 26, unit: 'g', unitName: 'oz, with skin, yields' }, - { value: 35, unit: 'g', unitName: 'crabapple' }, - { value: 125, unit: 'g', unitName: 'cup' }, - { value: 14, unit: 'g', unitName: 'ring' }, - { value: 17, unit: 'g', unitName: 'slice' }, - { value: 7, unit: 'g', unitName: 'thin slice' }, - { value: 149, unit: 'g', unitName: 'small (2-3/4" dia)' }, - { value: 223, unit: 'g', unitName: 'large (3-1/4" dia)' }, - { value: 2.6041666666666665, unit: 'g', unitName: 'tsp' }, - { value: 7.8125, unit: 'g', unitName: 'tbsp' }, - { value: 1, unit: 'g', unitName: 'gram' }, - ], - servingSizes: [ - { unitName: 'medium (3" dia)', quantity: 1 }, - { unitName: 'cup, quartered or chopped', quantity: 1 }, - { unitName: 'cup, sliced', quantity: 1 }, - { unitName: 'oz, with skin, yields', quantity: 1 }, - { unitName: 'crabapple', quantity: 1 }, - { unitName: 'cup', quantity: 1 }, - { unitName: 'ring', quantity: 1 }, - { unitName: 'slice', quantity: 1 }, - { unitName: 'thin slice', quantity: 1 }, - { unitName: 'small (2-3/4" dia)', quantity: 1 }, - { unitName: 'large (3-1/4" dia)', quantity: 1 }, - { unitName: 'gram', quantity: 100 }, - ], - foodItems: [ - { - passioID: '1603211585443', - name: 'Apple, raw', - imageName: '1003974', - selectedQuantity: 1, - selectedUnit: 'medium (3" dia)', - entityType: PassioIDEntityType.item, - servingUnits: [ - { value: 182, unit: 'g', unitName: 'medium (3" dia)' }, - { value: 125, unit: 'g', unitName: 'cup, quartered or chopped' }, - { value: 110, unit: 'g', unitName: 'cup, sliced' }, - { value: 26, unit: 'g', unitName: 'oz, with skin, yields' }, - { value: 35, unit: 'g', unitName: 'crabapple' }, - { value: 125, unit: 'g', unitName: 'cup' }, - { value: 14, unit: 'g', unitName: 'ring' }, - { value: 17, unit: 'g', unitName: 'slice' }, - { value: 7, unit: 'g', unitName: 'thin slice' }, - { value: 149, unit: 'g', unitName: 'small (2-3/4" dia)' }, - { value: 223, unit: 'g', unitName: 'large (3-1/4" dia)' }, - { value: 2.6041666666666665, unit: 'g', unitName: 'tsp' }, - { value: 7.8125, unit: 'g', unitName: 'tbsp' }, - { value: 1, unit: 'g', unitName: 'gram' }, - ], - servingSizes: [ - { unitName: 'medium (3" dia)', quantity: 1 }, - { unitName: 'cup, quartered or chopped', quantity: 1 }, - { unitName: 'cup, sliced', quantity: 1 }, - { unitName: 'oz, with skin, yields', quantity: 1 }, - { unitName: 'crabapple', quantity: 1 }, - { unitName: 'cup', quantity: 1 }, - { unitName: 'ring', quantity: 1 }, - { unitName: 'slice', quantity: 1 }, - { unitName: 'thin slice', quantity: 1 }, - { unitName: 'small (2-3/4" dia)', quantity: 1 }, - { unitName: 'large (3-1/4" dia)', quantity: 1 }, - { unitName: 'gram', quantity: 100 }, - ], - computedWeight: { value: 182, unit: 'g' }, - vitaminA: { unit: 'IU', value: 0 }, - alcohol: { unit: 'g', value: 0 }, - calcium: { unit: 'mg', value: 10.92 }, - calories: { unit: 'kcal', value: 94.64 }, - carbs: { unit: 'g', value: 25.134200000000003 }, - cholesterol: { unit: 'mg', value: 0 }, - fat: { unit: 'g', value: 0.3094 }, - iodine: { unit: 'ug', value: 0 }, - iron: { unit: 'mg', value: 0.2184 }, - magnesium: { unitName: 'mg', unit: 'mg', value: 9.1 }, - monounsaturatedFat: { - unit: 'mg', - unitName: 'g', - value: 0.012740000000000001, - }, - phosphorus: { unit: 'mg', value: 20.02 }, - polyunsaturatedFat: { unit: 'g', value: 0.09282 }, - potassium: { unit: 'mg', value: 194.74 }, - protein: { unit: 'g', value: 0.4732 }, - sodium: { unit: 'mg', value: 1.82 }, - sugarAlcohol: { unit: 'g', value: 0 }, - transFat: { unit: 'g', value: 0 }, - vitaminB12: { unit: 'ug', value: 0 }, - vitaminB12Added: { unit: 'ug', value: 0 }, - vitaminB6: { unit: 'mg', value: 0.07462 }, - vitaminC: { unit: 'mg', value: 8.372 }, - vitaminD: { unit: 'ug', value: 0 }, - vitaminE: { unit: 'mg', value: 0.3276 }, - vitaminEAdded: { unit: 'mg', value: 0 }, - }, - ], - }, -}; diff --git a/src/screens/quick/mode/barcode/BarcodeFoodScan.tsx b/src/screens/quick/mode/barcode/BarcodeFoodScan.tsx new file mode 100644 index 0000000..aeefc74 --- /dev/null +++ b/src/screens/quick/mode/barcode/BarcodeFoodScan.tsx @@ -0,0 +1,175 @@ +import React, { useMemo } from 'react'; +import { + Dimensions, + FlatList, + Platform, + StyleSheet, + TouchableOpacity, + View, +} from 'react-native'; + +import { QuickScanningLoadingView, QuickScanningResultView } from '../../views'; + +import { gestureHandlerRootHOC } from 'react-native-gesture-handler'; +import BottomSheet, { BottomSheetView } from '@gorhom/bottom-sheet'; +import { scaleHeight, screenHeight } from '../../../../utils'; +import { Text } from '../../../../components'; +import { useBranding } from '../../../../contexts'; +import AlternateFoodLogView from '../../../../components/alternatives/alternativesItem'; +import { QuickScanItemAddedToDiaryView } from '../../views/QuickSacnItemAddedToDiaryView'; +import { QuickScanLogButtonView } from '../../views/QuickScanLogButtonView'; +import { useBarcodeFoodScan } from './useBarcodeFoodScan'; +import { BarcodeNotDetect } from './BarcodeNotDetect'; + +interface Props { + onScanNutritionFacts: () => void; +} +export const BarcodeFoodScan = gestureHandlerRootHOC( + ({ onScanNutritionFacts }: Props) => { + const { + alternatives, + isLodgedFood, + isStopScan, + passioQuickResults, + onContinueScanningPress, + onFoodSearchManuallyPress, + onLogFoodPress, + onOpenFoodLogEditor, + onViewDiaryPress, + resetScanning, + setStopScan, + } = useBarcodeFoodScan(); + + const info = false; + + const branding = useBranding(); + + const snapPoints = useMemo( + () => [scaleHeight(Platform.OS === 'android' ? 260 : 260)], + [] + ); + + if (passioQuickResults && passioQuickResults.passioIDAttributes == null) { + return ( + + + + ); + } + + return ( + <> + {!isLodgedFood && !info && ( + {}} + index={isLodgedFood ? -1 : 0} + enableDynamicSizing + onChange={(index) => { + if (alternatives && alternatives?.length > 0) { + setStopScan(index === 1); + } + }} + snapPoints={snapPoints} + handleIndicatorStyle={{ + backgroundColor: branding.border, + }} + backgroundStyle={styles.bottomSheetChildrenContainer} + > + {passioQuickResults === null ? ( + + + + + ) : null} + + <> + {passioQuickResults !== undefined && + passioQuickResults !== null ? ( + + + {!isStopScan && alternatives && alternatives?.length > 0 + ? ' Pull tray up to see more options. ' + : ''} + + + {alternatives && alternatives?.length > 0 && ( + + Alternatives + + )} + ( + onOpenFoodLogEditor(item)} + > + + + )} + /> + + + ) : null} + + + )} + + {isLodgedFood === false && !info && passioQuickResults ? ( + onOpenFoodLogEditor(passioQuickResults)} + onSaveFoodLog={() => onLogFoodPress(passioQuickResults)} + onFoodSearchManuallyPress={onFoodSearchManuallyPress} + hideSearch + /> + ) : null} + + {isLodgedFood && ( + + )} + + ); + } +); + +const styles = StyleSheet.create({ + bottomSheetChildrenContainer: { + flex: 1, + }, + pullTray: { + alignSelf: 'center', + }, +}); diff --git a/src/screens/quick/mode/barcode/BarcodeNotDetect.tsx b/src/screens/quick/mode/barcode/BarcodeNotDetect.tsx new file mode 100644 index 0000000..70b8450 --- /dev/null +++ b/src/screens/quick/mode/barcode/BarcodeNotDetect.tsx @@ -0,0 +1,114 @@ +import React from 'react'; +import { Image, StyleSheet, View } from 'react-native'; + +import { BasicButton, Card, Text } from '../../../../components'; +import { ICONS } from '../../../../assets'; +import { screenHeight } from '../../../../utils'; + +interface Props { + onScanNutritionFacts?: () => void; + onCancelPress?: () => void; +} + +export const BarcodeNotDetect = ({ + onScanNutritionFacts, + onCancelPress, +}: Props) => { + return ( + + + + + + Barcode Not Recognized + + + Try scanning nutrition facts instead. + + + + + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + position: 'absolute', + left: 0, + right: 0, + top: screenHeight / 3, + alignItems: 'center', + alignContent: 'center', + alignSelf: 'center', + justifyContent: 'center', + }, + card: { + justifyContent: 'center', + alignItems: 'center', + paddingVertical: 16, + paddingHorizontal: 12, + marginHorizontal: 8, + alignContent: 'center', + + alignSelf: 'center', + }, + contentContainer: { + flexDirection: 'column', + alignSelf: 'center', + justifyContent: 'center', + alignContent: 'center', + alignItems: 'center', + }, + icon: { + height: 32, + width: 32, + marginVertical: 8, + }, + title: { + marginVertical: 3, + textAlign: 'center', + }, + description: { + marginBottom: 16, + marginVertical: 8, + fontSize: 14, + textAlign: 'center', + }, + buttonContainer: { + flexDirection: 'row', + justifyContent: 'center', + alignSelf: 'center', + alignItems: 'center', + }, + buttonContainers: { + flexDirection: 'row', + justifyContent: 'space-between', + alignContent: 'space-around', + marginVertical: 16, + }, + iconContainer: { + alignItems: 'center', + flex: 1, + }, + button: { + flex: 1, + marginHorizontal: 4, + }, +}); + +export default BarcodeNotDetect; diff --git a/src/screens/quick/mode/barcode/useBarcodeFoodScan.ts b/src/screens/quick/mode/barcode/useBarcodeFoodScan.ts new file mode 100644 index 0000000..1020f97 --- /dev/null +++ b/src/screens/quick/mode/barcode/useBarcodeFoodScan.ts @@ -0,0 +1,262 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { + useNavigation, + useRoute, + type RouteProp, +} from '@react-navigation/core'; +import { useServices } from '../../../../contexts'; +import type { FoodLog, QuickResult } from '../../../../models'; +import { ScreenType } from '../../../../models/ScreenType'; +import type { ParamList } from '../../../../navigaitons'; +import { + recordAnalyticsFoodLogs, + getLogToDate, + getMealLog, +} from '../../../../utils'; +import { + PassioSDK, + type BarcodeCandidate, + type FoodDetectionConfig, + type FoodDetectionEvent, + type PassioFoodItem, +} from '@passiolife/nutritionai-react-native-sdk-v3'; +import { + getBarcodeResult, + getDetectionCandidate, + getPassioIDAttribute, +} from '../../../../utils/QuickResultUtils'; +import { convertPassioFoodItemToFoodLog } from '../../../../utils/V3Utils'; +import type { ScanningScreenNavigationProps } from '../../QuickScanningScreen'; + +export const useBarcodeFoodScan = () => { + const services = useServices(); + const navigation = useNavigation(); + const passioQuickResultRef = useRef(null); + + const { params } = useRoute>(); + const date = getLogToDate(params.logToDate, params.logToMeal); + const meal = getMealLog(date, params.logToMeal); + + const [isStopScan, setStopScan] = useState(false); + const [isLodgedFood, setLoggedFood] = useState(false); + const [passioQuickResults, setPassioQuickResults] = + useState(null); + + const [alternatives, setPassioAlternatives] = useState( + [] + ); + + const [foodDetectEvents, setFoodDetectionEvent] = + useState(null); + + const getQuickResults = useCallback( + async (barcodeCandidate?: BarcodeCandidate) => { + if ( + barcodeCandidate && + passioQuickResultRef.current?.barcode === barcodeCandidate.barcode + ) { + return null; + } + + if (barcodeCandidate) { + return await getBarcodeResult(barcodeCandidate); + } + + return null; + }, + [passioQuickResultRef] + ); + + const resetScanning = useCallback(() => { + passioQuickResultRef.current = null; + setFoodDetectionEvent(null); + setPassioQuickResults(null); + setStopScan(false); + }, []); + + const onSavedLog = useCallback( + async (item: FoodLog) => { + await services.dataService.saveFoodLog(item); + await recordAnalyticsFoodLogs({ + id: item.refCode ?? '', + screen: ScreenType.quickScan, + foodLog: item, + }); + setStopScan(true); + setLoggedFood(true); + }, + [services.dataService] + ); + + // it's call when user swipe the scanning result card. + const onLogFoodPress = useCallback( + async (result: QuickResult) => { + const item = await getPassioIDAttribute(result); + if (item) { + const createdFoodLog = convertPassioFoodItemToFoodLog(item, date, meal); + await onSavedLog(createdFoodLog); + } + }, + [date, meal, onSavedLog] + ); + + const onEditFoodLogPress = useCallback( + async (attribute: PassioFoodItem) => { + if (attribute) { + setStopScan(true); + navigation.navigate('EditFoodLogScreen', { + foodLog: convertPassioFoodItemToFoodLog(attribute, date, meal), + prevRouteName: 'QuickScan', + onCancelPress: () => { + setStopScan(false); + }, + onSaveLogPress: () => { + setStopScan(false); + setLoggedFood(true); + }, + }); + } + }, + [date, meal, navigation] + ); + + // Convert PassioIDAttributes to foodLog + const onOpenFoodLogEditor = useCallback( + async (result: QuickResult) => { + const item = await getPassioIDAttribute(result); + if (item) { + setStopScan(true); + navigation.navigate('EditFoodLogScreen', { + foodLog: convertPassioFoodItemToFoodLog(item, date, meal), + prevRouteName: 'QuickScan', + onCancelPress: () => { + setStopScan(false); + }, + onSaveLogPress: () => { + setStopScan(false); + setLoggedFood(true); + }, + }); + } + }, + [date, meal, navigation] + ); + + const onFoodSearchManuallyPress = useCallback(async () => { + navigation.navigate('FoodSearchScreen', { + from: 'Other', + onSaveData: (attribute: PassioFoodItem) => { + onEditFoodLogPress(attribute); + }, + }); + }, [onEditFoodLogPress, navigation]); + + const onContinueScanningPress = () => { + setLoggedFood(false); + setStopScan(false); + setFoodDetectionEvent(null); + setPassioQuickResults(null); + passioQuickResultRef.current = null; + }; + const onViewDiaryPress = () => { + navigation.pop(1); + navigation.navigate('BottomNavigation', { + screen: 'MealLogScreen', + }); + }; + + useEffect(() => { + const config: FoodDetectionConfig = { + detectBarcodes: true, + detectPackagedFood: false, + }; + let subscription = PassioSDK.startFoodDetection( + config, + (detection: FoodDetectionEvent) => { + if ( + detection && + detection?.candidates?.barcodeCandidates && + detection.candidates?.barcodeCandidates?.length > 0 + ) { + const barcode = detection.candidates.barcodeCandidates?.[0]?.barcode; + + if ( + barcode !== undefined && + barcode === passioQuickResultRef.current?.barcode + ) { + return; + } + + setFoodDetectionEvent(detection); + } + } + ); + + return () => { + subscription?.remove(); + }; + }, []); + + useEffect(() => { + const detection = foodDetectEvents; + async function init() { + if (detection) { + const { candidates } = detection; + + if (candidates) { + const detectedCandidate = candidates.detectedCandidates?.[0]; + let attribute: QuickResult | null = await getQuickResults( + candidates.barcodeCandidates?.[0] + ); + + /** Now Check attribute and alternative */ + + if ( + attribute && + attribute?.passioID !== passioQuickResultRef.current?.passioID + ) { + setPassioQuickResults(attribute); + passioQuickResultRef.current = attribute; + + /** Alternative */ + const alternative = + detectedCandidate?.alternatives && + detectedCandidate.alternatives.length > 0 + ? detectedCandidate.alternatives + : candidates.detectedCandidates; + + const candidateAlternatives = alternative + .map(getDetectionCandidate) + .filter( + (item) => item?.passioID !== attribute?.passioID + ) as QuickResult[]; + setPassioAlternatives(candidateAlternatives); + } + } + } + } + + if (isStopScan === true) { + return; + } + + if (detection) { + init(); + } + }, [foodDetectEvents, getQuickResults, isStopScan]); + + return { + alternatives, + isLodgedFood, + isStopScan, + passioQuickResults, + onContinueScanningPress, + onFoodSearchManuallyPress, + onLogFoodPress, + onOpenFoodLogEditor, + onViewDiaryPress, + resetScanning, + setStopScan, + }; +}; diff --git a/src/screens/quick/mode/nutritionFact/NutritionFactScan.tsx b/src/screens/quick/mode/nutritionFact/NutritionFactScan.tsx new file mode 100644 index 0000000..6961c62 --- /dev/null +++ b/src/screens/quick/mode/nutritionFact/NutritionFactScan.tsx @@ -0,0 +1,94 @@ +import React, { useMemo } from 'react'; +import { Platform, StyleSheet, View } from 'react-native'; + +import { QuickScanningLoadingView } from './../../views'; + +import NutritionFactView from './../../views/NutritionFactView'; +import { gestureHandlerRootHOC } from 'react-native-gesture-handler'; +import BottomSheet, { BottomSheetView } from '@gorhom/bottom-sheet'; +import { scaleHeight } from '../../../../utils'; +import { useBranding } from '../../../../contexts'; +import { useNutritionFactScan } from './useNutritionFactScan'; + +export const NutritionFactScan = gestureHandlerRootHOC(() => { + const { + nutritionFacts, + onSaveFoodLogUsingNutrientFact, + onUpdatingNutritionFacFlag, + resetScanning, + } = useNutritionFactScan(); + + const branding = useBranding(); + + const snapPoints = useMemo( + () => [scaleHeight(Platform.OS === 'android' ? 260 : 240)], + [] + ); + + return ( + <> + {}} + index={0} + enableDynamicSizing + snapPoints={snapPoints} + handleIndicatorStyle={{ + backgroundColor: nutritionFacts ? 'white' : branding.border, + }} + backgroundStyle={styles.bottomSheetChildrenContainer} + > + {nutritionFacts === null ? ( + + + + + + ) : null} + + {nutritionFacts !== null && ( + resetScanning()} + onPreventToUpdatingNutritionFact={onUpdatingNutritionFacFlag} + onNext={onSaveFoodLogUsingNutrientFact} + /> + )} + + + ); +}); + +const styles = StyleSheet.create({ + touchAreaStyle: { + flex: 1, + position: 'absolute', + height: '100%', + width: '100%', + }, + scanIcon: { + position: 'absolute', + top: 0, + bottom: 50, + right: 0, + left: 0, + marginHorizontal: 16, + alignItems: 'center', + justifyContent: 'center', + }, + bottomSheetChildrenContainer: { + flex: 1, + }, + pullTray: { + alignSelf: 'center', + }, + camera: { + flex: 1, + width: '100%', + }, + container: { + flex: 1, + backgroundColor: 'black', + width: '100%', + }, +}); diff --git a/src/screens/quick/mode/nutritionFact/useNutritionFactScan.ts b/src/screens/quick/mode/nutritionFact/useNutritionFactScan.ts new file mode 100644 index 0000000..a658894 --- /dev/null +++ b/src/screens/quick/mode/nutritionFact/useNutritionFactScan.ts @@ -0,0 +1,66 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; + +import type { QuickResult } from '../../../../models'; +import { + NutritionDetectionEvent, + PassioSDK, + type NutritionFacts, +} from '@passiolife/nutritionai-react-native-sdk-v3'; +import { ShowToast } from '../../../../utils'; +import { useNavigation } from '@react-navigation/native'; +import type { ScanningScreenNavigationProps } from '../../QuickScanningScreen'; +import { createCustomFoodUsingNutritionFact } from '../../../../screens/foodCreator/FoodCreator.utils'; + +export const useNutritionFactScan = () => { + const passioQuickResultRef = useRef(null); + + const [nutritionFacts, setNutritionFacts] = useState( + null + ); + const navigation = useNavigation(); + + const resetScanning = useCallback(() => { + passioQuickResultRef.current = null; + setNutritionFacts(null); + }, []); + + const onUpdatingNutritionFacFlag = useCallback(() => { + ShowToast('Coming soon'); + }, []); + + // Convert NutritionFacts to foodLog + const onSaveFoodLogUsingNutrientFact = useCallback( + (nutrientFact: NutritionFacts, _name: string) => { + if (nutrientFact) { + const customFood = createCustomFoodUsingNutritionFact(nutrientFact); + navigation.navigate('FoodCreatorScreen', { + foodLog: customFood, + from: 'QuickScan', + }); + } + setNutritionFacts(null); + }, + [navigation] + ); + + useEffect(() => { + let subscription = PassioSDK.startNutritionFactsDetection( + (detection: NutritionDetectionEvent) => { + if (detection && detection.nutritionFacts) { + setNutritionFacts(detection.nutritionFacts); + } + } + ); + + return () => { + subscription?.remove(); + }; + }, []); + + return { + nutritionFacts, + onSaveFoodLogUsingNutrientFact, + onUpdatingNutritionFacFlag, + resetScanning, + }; +}; diff --git a/src/screens/quick/mode/visual/VisualFoodScan.tsx b/src/screens/quick/mode/visual/VisualFoodScan.tsx new file mode 100644 index 0000000..b084a9a --- /dev/null +++ b/src/screens/quick/mode/visual/VisualFoodScan.tsx @@ -0,0 +1,168 @@ +import React, { useMemo } from 'react'; +import { + Dimensions, + FlatList, + Platform, + StyleSheet, + TouchableOpacity, + View, +} from 'react-native'; + +import { QuickScanningLoadingView, QuickScanningResultView } from '../../views'; + +import { gestureHandlerRootHOC } from 'react-native-gesture-handler'; +import BottomSheet, { BottomSheetView } from '@gorhom/bottom-sheet'; +import { scaleHeight } from '../../../../utils'; +import { Text } from '../../../../components'; +import { useBranding } from '../../../../contexts'; +import AlternateFoodLogView from '../../../../components/alternatives/alternativesItem'; +import { QuickScanItemAddedToDiaryView } from '../../views/QuickSacnItemAddedToDiaryView'; +import { QuickScanLogButtonView } from '../../views/QuickScanLogButtonView'; +import { useVisualFoodScan } from './useVisualFoodScan'; + +export const VisualFoodScan = gestureHandlerRootHOC(() => { + const { + alternatives, + isLodgedFood, + isStopScan, + passioQuickResults, + onContinueScanningPress, + onFoodSearchManuallyPress, + onLogFoodPress, + onOpenFoodLogEditor, + onViewDiaryPress, + resetScanning, + setStopScan, + } = useVisualFoodScan(); + + const branding = useBranding(); + const info = false; + + const snapPoints = useMemo( + () => [scaleHeight(Platform.OS === 'android' ? 260 : 260)], + [] + ); + + return ( + <> + {!isLodgedFood && !info && ( + {}} + index={isLodgedFood ? -1 : 0} + enableDynamicSizing + onChange={(index) => { + if (alternatives && alternatives?.length > 0) { + setStopScan(index === 1); + } + }} + snapPoints={snapPoints} + handleIndicatorStyle={{ + backgroundColor: branding.border, + }} + backgroundStyle={styles.bottomSheetChildrenContainer} + > + {passioQuickResults === null ? ( + + + + + ) : null} + + <> + {passioQuickResults !== undefined && passioQuickResults !== null ? ( + + + {!isStopScan && alternatives && alternatives?.length > 0 + ? ' Pull tray up to see more options. ' + : ''} + + + {alternatives && alternatives?.length > 0 && ( + + Alternatives + + )} + ( + onOpenFoodLogEditor(item)} + > + + + )} + /> + + + ) : null} + + + )} + + {isLodgedFood === false && !info && passioQuickResults ? ( + onOpenFoodLogEditor(passioQuickResults)} + onSaveFoodLog={() => onLogFoodPress(passioQuickResults)} + onFoodSearchManuallyPress={onFoodSearchManuallyPress} + /> + ) : null} + + {isLodgedFood && ( + + )} + + ); +}); + +const styles = StyleSheet.create({ + touchAreaStyle: { + flex: 1, + position: 'absolute', + height: '100%', + width: '100%', + }, + scanIcon: { + position: 'absolute', + top: 0, + bottom: 50, + right: 0, + left: 0, + marginHorizontal: 16, + alignItems: 'center', + justifyContent: 'center', + }, + bottomSheetChildrenContainer: { + flex: 1, + }, + pullTray: { + alignSelf: 'center', + }, + camera: { + flex: 1, + width: '100%', + }, + container: {}, +}); diff --git a/src/screens/quick/useQuickScan.ts b/src/screens/quick/mode/visual/useVisualFoodScan.ts similarity index 73% rename from src/screens/quick/useQuickScan.ts rename to src/screens/quick/mode/visual/useVisualFoodScan.ts index f8488ec..d5bd55f 100644 --- a/src/screens/quick/useQuickScan.ts +++ b/src/screens/quick/mode/visual/useVisualFoodScan.ts @@ -5,32 +5,32 @@ import { useRoute, type RouteProp, } from '@react-navigation/core'; -import { useServices } from '../../contexts'; -import type { FoodLog, QuickResult } from '../../models'; -import { ScreenType } from '../../models/ScreenType'; -import type { ParamList } from '../../navigaitons'; -import { recordAnalyticsFoodLogs, getLogToDate, getMealLog } from '../../utils'; -import type { ScanningScreenNavigationProps } from './QuickScanningScreen'; +import { useServices } from '../../../../contexts'; +import type { FoodLog, QuickResult } from '../../../../models'; +import { ScreenType } from '../../../../models/ScreenType'; +import type { ParamList } from '../../../../navigaitons'; +import { + recordAnalyticsFoodLogs, + getLogToDate, + getMealLog, +} from '../../../../utils'; import { PassioSDK, - type BarcodeCandidate, type DetectedCandidate, type FoodDetectionConfig, type FoodDetectionEvent, - type NutritionFacts, type PackagedFoodCode, type PassioFoodItem, } from '@passiolife/nutritionai-react-native-sdk-v3'; import { - getBarcodeResult, getDetectionCandidate, getPackageFoodResult, getPassioIDAttribute, -} from '../../utils/QuickResultUtils'; -import { ShowToast } from '../../utils'; -import { convertPassioFoodItemToFoodLog } from '../../utils/V3Utils'; +} from '../../../../utils/QuickResultUtils'; +import { convertPassioFoodItemToFoodLog } from '../../../../utils/V3Utils'; +import type { ScanningScreenNavigationProps } from './../../QuickScanningScreen'; -export const useQuickScan = () => { +export const useVisualFoodScan = () => { const services = useServices(); const navigation = useNavigation(); const passioQuickResultRef = useRef(null); @@ -39,7 +39,6 @@ export const useQuickScan = () => { const date = getLogToDate(params.logToDate, params.logToMeal); const meal = getMealLog(date, params.logToMeal); - const [info, setInfo] = useState(false); const [isStopScan, setStopScan] = useState(false); const [isLodgedFood, setLoggedFood] = useState(false); const [passioQuickResults, setPassioQuickResults] = @@ -48,24 +47,15 @@ export const useQuickScan = () => { const [alternatives, setPassioAlternatives] = useState( [] ); - const [nutritionFacts, setNutritionFacts] = useState( - null - ); + const [foodDetectEvents, setFoodDetectionEvent] = useState(null); const getQuickResults = useCallback( async ( - barcodeCandidate?: BarcodeCandidate, packagedFoodCode?: PackagedFoodCode, detectedCandidate?: DetectedCandidate ) => { - if ( - barcodeCandidate && - passioQuickResultRef.current?.barcode === barcodeCandidate.barcode - ) { - return null; - } if ( packagedFoodCode && passioQuickResultRef.current?.packageFood === packagedFoodCode @@ -79,9 +69,6 @@ export const useQuickScan = () => { return null; } - if (barcodeCandidate) { - return await getBarcodeResult(barcodeCandidate); - } if (packagedFoodCode) { return await getPackageFoodResult(packagedFoodCode); } @@ -97,26 +84,15 @@ export const useQuickScan = () => { const resetScanning = useCallback(() => { passioQuickResultRef.current = null; setFoodDetectionEvent(null); - setNutritionFacts(null); setPassioQuickResults(null); setStopScan(false); }, []); - // it's call when user tap on closed icon. - const onClosed = useCallback(() => { - resetScanning(); - navigation.goBack(); - }, [navigation, resetScanning]); - - const onUpdatingNutritionFacFlag = useCallback(() => { - ShowToast('Coming soon'); - }, []); - const onSavedLog = useCallback( async (item: FoodLog) => { await services.dataService.saveFoodLog(item); await recordAnalyticsFoodLogs({ - id: item.refCode ?? item.passioID, + id: item.refCode ?? '', screen: ScreenType.quickScan, foodLog: item, }); @@ -189,20 +165,11 @@ export const useQuickScan = () => { }); }, [onEditFoodLogPress, navigation]); - // Convert NutritionFacts to foodLog - const onSaveFoodLogUsingNutrientFact = useCallback( - (_nutrientFact: NutritionFacts, _name: string) => { - setNutritionFacts(null); - }, - [] - ); - const onContinueScanningPress = () => { setLoggedFood(false); setStopScan(false); setFoodDetectionEvent(null); setPassioQuickResults(null); - setNutritionFacts(null); passioQuickResultRef.current = null; }; const onViewDiaryPress = () => { @@ -214,32 +181,21 @@ export const useQuickScan = () => { useEffect(() => { const config: FoodDetectionConfig = { - detectBarcodes: true, + detectBarcodes: false, detectPackagedFood: true, }; let subscription = PassioSDK.startFoodDetection( config, (detection: FoodDetectionEvent) => { if ( - (detection && - detection?.candidates?.barcodeCandidates && - detection.candidates?.barcodeCandidates?.length > 0) || - (detection?.candidates?.detectedCandidates && - detection.candidates?.detectedCandidates?.length > 0) || - (detection?.candidates?.packagedFoodCode && - detection.candidates?.packagedFoodCode?.length > 0) + detection && + detection?.candidates?.detectedCandidates && + detection.candidates?.detectedCandidates?.length > 0 ) { - const barcode = detection.candidates.barcodeCandidates?.[0]?.barcode; const packageFood = detection.candidates.packagedFoodCode?.[0]; const passioID = detection.candidates.detectedCandidates?.[0]?.passioID; - if ( - barcode !== undefined && - barcode === passioQuickResultRef.current?.barcode - ) { - return; - } if ( packageFood !== undefined && packageFood === passioQuickResultRef.current?.packageFood @@ -273,7 +229,6 @@ export const useQuickScan = () => { if (candidates) { const detectedCandidate = candidates.detectedCandidates?.[0]; let attribute: QuickResult | null = await getQuickResults( - candidates.barcodeCandidates?.[0], candidates.packagedFoodCode?.[0], detectedCandidate ); @@ -312,25 +267,19 @@ export const useQuickScan = () => { if (detection) { init(); } - }, [foodDetectEvents, getQuickResults, isStopScan, nutritionFacts]); + }, [foodDetectEvents, getQuickResults, isStopScan]); return { alternatives, - info, isLodgedFood, isStopScan, - nutritionFacts, passioQuickResults, - onClosed, onContinueScanningPress, onFoodSearchManuallyPress, onLogFoodPress, onOpenFoodLogEditor, - onSaveFoodLogUsingNutrientFact, - onUpdatingNutritionFacFlag, onViewDiaryPress, resetScanning, - setInfo, setStopScan, }; }; diff --git a/src/screens/quick/views/NutritionFactView.tsx b/src/screens/quick/views/NutritionFactView.tsx index ad3f858..f25532d 100644 --- a/src/screens/quick/views/NutritionFactView.tsx +++ b/src/screens/quick/views/NutritionFactView.tsx @@ -1,5 +1,4 @@ import { - Alert, type KeyboardTypeOptions, StyleSheet, TouchableOpacity, @@ -10,7 +9,6 @@ import React, { useState } from 'react'; import { EditValueAlertPrompt } from '../alerts/UpdateNutrtionUnitValueAlert'; import type { NutritionFacts } from '@passiolife/nutritionai-react-native-sdk-v3/src/sdk/v2'; -import { SaveFoodAlert } from '../alerts/SaveFoodAlert'; import { useBranding } from '../../../contexts/branding/BrandingContext'; interface Props { @@ -33,7 +31,6 @@ const NutritionFactView = (props: Props) => { const copyOfNutritionFact = { ...props.nutritionFact }; const [openEditAlertType, setEditOpenEditAlertType] = useState(null); - const [isOpenSaveFoodAlert, setOpenSaveFoodAlert] = useState(false); const [nutrientAlertValue] = useState(undefined); const [nutrientAlertTitle] = useState(''); const [keyboardType] = useState(); @@ -41,16 +38,6 @@ const NutritionFactView = (props: Props) => { const dismissNutrientEditAlert = () => { setEditOpenEditAlertType(null); }; - const dismissSaveFoodAlert = () => { - setOpenSaveFoodAlert(false); - }; - const openSaveFoodAlert = () => { - setOpenSaveFoodAlert(true); - }; - - const onNext = (_title: string | undefined) => { - Alert.alert('Under development'); - }; const BottomActionView = () => { return ( @@ -68,7 +55,7 @@ const NutritionFactView = (props: Props) => { text="Next" small secondary={false} - onPress={() => openSaveFoodAlert()} + onPress={() => props.onNext(props.nutritionFact, '')} /> ); @@ -154,12 +141,6 @@ const NutritionFactView = (props: Props) => { hint={'Enter value'} /> ) : null} - onNext(input)} - onClose={async () => dismissSaveFoodAlert()} - isVisible={isOpenSaveFoodAlert} - /> ); }; diff --git a/src/screens/quick/views/QuickScanInfo.tsx b/src/screens/quick/views/QuickScanInfo.tsx index db2fb84..1b24acc 100644 --- a/src/screens/quick/views/QuickScanInfo.tsx +++ b/src/screens/quick/views/QuickScanInfo.tsx @@ -10,8 +10,10 @@ interface Props { export const QuickScanInfo = ({ onOkPress }: Props) => { const renderIcon = (title: string, icon: number) => { return ( - - + + + + {title} @@ -28,15 +30,14 @@ export const QuickScanInfo = ({ onOkPress }: Props) => { What you can scan - You can scan your foods using a variety of ways: + You can quickly switch between scanning modes to log your food a + variety of ways - {renderIcon('Foods', ICONS.foodScannerFoods)} - {renderIcon('Beverages', ICONS.foodScannerBeverage)} - {renderIcon('Packaging', ICONS.foodScannerPackaging)} - {renderIcon('Nutrition Facts', ICONS.foodScannerFacts)} - {renderIcon('Barcodes', ICONS.foodScannerBarcode)} + {renderIcon('Whole Foods Mode', ICONS.modeVisual)} + {renderIcon('Barcode Mode', ICONS.modeBarcode)} + {renderIcon('Nutrition Facts Mode', ICONS.modeNutritionFact)} void; onSaveFoodLog?: () => void; onFoodSearchManuallyPress?: () => void; + hideSearch?: boolean; } export const QuickScanLogButtonView = ({ onOpenFoodLogEditor, onSaveFoodLog, onFoodSearchManuallyPress, + hideSearch, }: Props) => { return ( - - - {' Not what you’re looking for? '} - - - Search Manually - - + + {' Not what you’re looking for? '} + + + Search Manually + + + )} { +interface Props { + text?: string; +} +export const QuickScanningLoadingView = ({ + text = 'Place your food within the frame', +}: Props) => { return ( - + - + Scanning... - Place your food within the frame + {text} - + ); }; @@ -26,6 +31,8 @@ const styles = StyleSheet.create({ container: { flexDirection: 'row', marginTop: scaleHeight(16), + paddingVertical: 16, + marginHorizontal: 24, }, loader: { marginStart: scaleWidth(24), diff --git a/src/screens/quick/views/QuickScanningResultView.tsx b/src/screens/quick/views/QuickScanningResultView.tsx index 4507b5b..e353dcf 100644 --- a/src/screens/quick/views/QuickScanningResultView.tsx +++ b/src/screens/quick/views/QuickScanningResultView.tsx @@ -1,4 +1,5 @@ import { + Image, TouchableOpacity, View, type ImageStyle, @@ -9,12 +10,14 @@ import React from 'react'; import { PassioFoodIcon } from '../../../components/passio/PassioFoodIcon'; import { PassioIDEntityType } from '@passiolife/nutritionai-react-native-sdk-v3/src/sdk/v2'; -import { Text } from '../../../components'; +import { Card, Text } from '../../../components'; import type { QuickResult } from '../../../models'; +import { ICONS } from '../../../assets'; interface Props { result: QuickResult; onOpenFoodLogEditor: (passioIDAttributes: QuickResult) => void; + onFoodLog: (passioIDAttributes: QuickResult) => void; onClear?: () => void; } @@ -22,7 +25,7 @@ export const QuickScanningResultView = React.memo((props: Props) => { const result = props.result; return ( - + { @@ -34,6 +37,7 @@ export const QuickScanningResultView = React.memo((props: Props) => { @@ -42,16 +46,45 @@ export const QuickScanningResultView = React.memo((props: Props) => { {props.result.name} + {props.result.barcode && ( + + {`UPC: ${props.result.barcode}`} + + )} + { + props.onFoodLog(props.result); + }} + > + + - + ); }); // Scanning Result Styles.... const scanningResultBaseContainerStyle: ViewStyle = { marginHorizontal: 10, + marginTop: 10, }; const foodLogContainerStyle: ViewStyle = { diff --git a/src/screens/recipeEditor/RecipeEditor/useRecipeEditor.ts b/src/screens/recipeEditor/RecipeEditor/useRecipeEditor.ts index 869e905..30e14a9 100644 --- a/src/screens/recipeEditor/RecipeEditor/useRecipeEditor.ts +++ b/src/screens/recipeEditor/RecipeEditor/useRecipeEditor.ts @@ -61,11 +61,11 @@ export function useRecipeEditor( const updateIngredientsItem = (foodItem: FoodItem) => { setIngredients( - addOrUpdateIngredients(foodItem.passioID, foodItem, ingredients) + addOrUpdateIngredients(foodItem.refCode, foodItem, ingredients) ); }; const deleteIngredientsItem = (foodItem: FoodItem) => { - setIngredients(deleteIngredient(foodItem.passioID, ingredients)); + setIngredients(deleteIngredient(foodItem.refCode, ingredients)); }; const updateRecipeName = (name: string) => { @@ -112,21 +112,18 @@ export function useRecipeEditor( }; function deleteIngredient( - passioID: PassioID, + refCode: PassioID, foodItems: FoodItem[] ): FoodItem[] { - return foodItems.filter((value) => value.passioID !== passioID); + return foodItems.filter((value) => value.refCode !== refCode); } function addOrUpdateIngredients( - passioID: PassioID, + refCode: PassioID, foodItem: FoodItem, foodItems: FoodItem[] ): FoodItem[] { - return [ - ...foodItems.filter((o) => o.passioID !== passioID), - { ...foodItem }, - ]; + return [...foodItems.filter((o) => o.refCode !== refCode), { ...foodItem }]; } return { diff --git a/src/screens/recipeEditor/RecipesScan/modal/EditIngredientsModal.tsx b/src/screens/recipeEditor/RecipesScan/modal/EditIngredientsModal.tsx index 0e8360d..35354c8 100644 --- a/src/screens/recipeEditor/RecipesScan/modal/EditIngredientsModal.tsx +++ b/src/screens/recipeEditor/RecipesScan/modal/EditIngredientsModal.tsx @@ -1,4 +1,4 @@ -import { AlternativeFoodLogsView, BasicButton } from '../../../../components'; +import { BasicButton } from '../../../../components'; import { Dimensions, Modal, @@ -16,8 +16,6 @@ import { COLORS } from '../../../../constants'; import EditServingAmountView from '../../../editFoodLogs/views/EditServingAmountView'; import type { FoodItem } from '../../../../models'; import LogInformationView from '../../../editFoodLogs/views/logInformationsView'; -import type { PassioIDAttributes } from '@passiolife/nutritionai-react-native-sdk-v3/src/sdk/v2'; -import { convertPassioIDAttributesToFoodItem } from '../../../../utils'; interface EditIngredientsModalProps { isEditModalOpen: boolean; @@ -34,10 +32,6 @@ const EditIngredientsModal = (props: EditIngredientsModalProps) => { const [isUpdateAlertPromptOpen, setUpdateAlertPromptOpen] = useState(false); - const onSwitchAlternative = async (attributes: PassioIDAttributes) => { - setFoodItem(convertPassioIDAttributesToFoodItem(attributes)); - }; - const saveIngredient = () => { props.updateIngredientsItem(foodItem); }; @@ -95,10 +89,9 @@ const EditIngredientsModal = (props: EditIngredientsModalProps) => { > { } }} /> - - {bottomActionView()} { return recipes.filter((value) => value.uuid !== uuId); }; export const addOrUpdateIngredients = ( - passioID: PassioID, + refCode: PassioID, foodItem: FoodItem, foodItems: FoodItem[] ): FoodItem[] => { - return [...foodItems.filter((o) => o.passioID !== passioID), { ...foodItem }]; + return [...foodItems.filter((o) => o.refCode !== refCode), { ...foodItem }]; }; export const deleteIngredient = ( - passioID: PassioID, + refCode: PassioID, foodItems: FoodItem[] ): FoodItem[] => { - return foodItems.filter((value) => value.passioID !== passioID); + return foodItems.filter((value) => value.refCode !== refCode); }; export const macroPerService = ( foodItems: FoodItem[], diff --git a/src/screens/voiceLogging/VoiceLoggingScreen.tsx b/src/screens/voiceLogging/VoiceLoggingScreen.tsx index 18b92d7..cdc9317 100644 --- a/src/screens/voiceLogging/VoiceLoggingScreen.tsx +++ b/src/screens/voiceLogging/VoiceLoggingScreen.tsx @@ -31,7 +31,7 @@ export const VoiceLoggingScreen = gestureHandlerRootHOC(() => { bottomSheetModalRef, snapPoints, searchQuery, - PassioSpeechRecognitionResult, + voiceRecords, isFetchingResponse, isRecording, onRecordingPress, @@ -118,7 +118,7 @@ export const VoiceLoggingScreen = gestureHandlerRootHOC(() => { onLogSelect={onLogSelectPress} onTryAgain={onTryAgainPress} onSearchManuallyPress={onSearchManuallyPress} - passioSpeechRecognitionResults={PassioSpeechRecognitionResult ?? []} + result={voiceRecords ?? []} /> diff --git a/src/screens/voiceLogging/useVoiceLoggingScreen.ts b/src/screens/voiceLogging/useVoiceLoggingScreen.ts index 6112fa4..de4c88c 100644 --- a/src/screens/voiceLogging/useVoiceLoggingScreen.ts +++ b/src/screens/voiceLogging/useVoiceLoggingScreen.ts @@ -29,6 +29,10 @@ export type VoiceLoggingScreenNavigationProps = StackNavigationProp< 'VoiceLoggingScreen' >; +export interface VoiceLogRecord extends PassioSpeechRecognitionModel { + isSelected?: boolean; +} + export function useVoiceLogging() { const services = useServices(); const navigation = useNavigation(); @@ -41,17 +45,25 @@ export function useVoiceLogging() { const [searchQuery, setSearchQuery] = useState(''); const searchQueryRef = useRef(''); - const [PassioSpeechRecognitionResult, setPassioSpeechRecognitionModel] = - useState(null); + const [voiceRecords, setVoiceRecords] = useState( + null + ); const recognizeSpeechRemote = useCallback(async (text: string) => { try { setFetchResponse(true); - setPassioSpeechRecognitionModel(null); + setVoiceRecords(null); const val = await PassioSDK.recognizeSpeechRemote(text); if (val && val.length > 0) { bottomSheetModalRef.current?.expand(); - setPassioSpeechRecognitionModel(val); + setVoiceRecords( + val.map((o) => { + return { + ...o, + isSelected: true, + }; + }) + ); } else { setSearchQuery(''); ShowToast("Sorry we didn't recognize your input, please try again"); @@ -87,7 +99,7 @@ export function useVoiceLogging() { }; const onClearPress = () => { - setPassioSpeechRecognitionModel(null); + setVoiceRecords(null); }; const onLogSelectPress = async (selected: PassioSpeechRecognitionModel[]) => { @@ -171,7 +183,7 @@ export function useVoiceLogging() { }, []); return { - PassioSpeechRecognitionResult, + voiceRecords, bottomSheetModalRef, snapPoints, isRecording, diff --git a/src/screens/voiceLogging/views/VoiceLoggingResult.tsx b/src/screens/voiceLogging/views/VoiceLoggingResult.tsx index 53a1d41..d365dbf 100644 --- a/src/screens/voiceLogging/views/VoiceLoggingResult.tsx +++ b/src/screens/voiceLogging/views/VoiceLoggingResult.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { type StyleProp, StyleSheet, @@ -15,10 +15,11 @@ import { BasicButton } from '../../../components'; import { ICONS } from '../../../assets'; import { FlatList } from 'react-native-gesture-handler'; import { getUpdatedCaloriesOfPassioAdvisorFoodInfo } from '../../../utils'; +import type { VoiceLogRecord } from '../useVoiceLoggingScreen'; interface Props { style?: StyleProp; - passioSpeechRecognitionResults: Array; + result: Array; onTryAgain: () => void; onSearchManuallyPress: () => void; onLogSelect: (selected: PassioSpeechRecognitionModel[]) => void; @@ -27,48 +28,50 @@ export interface VoiceLoggingResultRef {} export const VoiceLoggingResult = React.forwardRef( ( - { - style, - passioSpeechRecognitionResults, - onTryAgain, - onLogSelect, - onSearchManuallyPress, - }: Props, + { style, result, onTryAgain, onLogSelect, onSearchManuallyPress }: Props, _ref: React.Ref ) => { - const [selected, setSelected] = useState( - [] - ); + const [voiceRecords, setVoiceRecords] = useState(result); + + useEffect(() => { + setVoiceRecords(result); + }, [result]); - const onFoodSelect = (result: PassioSpeechRecognitionModel) => { - const find = selected?.find( - (item) => - item.advisorInfo?.recognisedName === - result.advisorInfo?.recognisedName + const onFoodSelect = (record: VoiceLogRecord) => { + setVoiceRecords((item) => + item?.map((i) => { + if ( + i.advisorInfo?.recognisedName === record.advisorInfo?.recognisedName + ) { + return { + ...i, + isSelected: !(i.isSelected ?? false), + }; + } else { + return i; + } + }) ); - if (find) { - setSelected((item) => - item?.filter( - (i) => - i.advisorInfo?.recognisedName !== - result.advisorInfo?.recognisedName - ) - ); - } else { - setSelected((item) => [...(item ?? []), result]); - } }; const onClearPress = () => { - setSelected([]); + setVoiceRecords((item) => + item?.map((i) => { + return { + ...i, + isSelected: false, + }; + }) + ); }; + const isSelected = voiceRecords?.find((i) => i.isSelected) !== undefined; return ( - {selected && selected.length > 0 ? 'Clear' : ''} + {isSelected ? 'Clear' : ''} @@ -90,30 +93,26 @@ export const VoiceLoggingResult = React.forwardRef( { + renderItem={({ item }: { item: VoiceLogRecord }) => { const foodDataInfo = item.advisorInfo?.foodDataInfo; - const isSelected = - selected?.find( - (it) => - it.advisorInfo?.recognisedName === - item.advisorInfo?.recognisedName - ) !== undefined; - const { calories } = getUpdatedCaloriesOfPassioAdvisorFoodInfo( item.advisorInfo ); return ( { onFoodSelect(item); }} - isSelected={isSelected} + isSelected={item.isSelected ?? false} /> ); }} @@ -148,10 +147,10 @@ export const VoiceLoggingResult = React.forwardRef( /> { - onLogSelect(selected ?? []); + onLogSelect(voiceRecords.filter((i) => i.isSelected)); }} style={styles.buttonLogSelected} - enable={selected && selected.length > 0} + enable={isSelected} text="Log Selected" /> diff --git a/src/utils/PassioUtils.ts b/src/utils/PassioUtils.ts index 2c67eb3..3897ef3 100644 --- a/src/utils/PassioUtils.ts +++ b/src/utils/PassioUtils.ts @@ -76,10 +76,9 @@ export function createFoodLogFromPassioIDAttributes( return { name: attributes.name, uuid: uuid, - passioID: attributes.passioID, - imageName: attributes.imageName, entityType: attributes.entityType, meal: meal, + refCode: '', eventTimestamp: dateFormat, selectedQuantity: convertSelectedQuantity(foodItem ?? recipe), selectedUnit: convertSelectedUnit(foodItem ?? recipe), @@ -159,6 +158,7 @@ export function foodItemsFromAttributes({ export function convertPassioFoodItem(foodItem: PassioFoodItem): FoodItem { return { ...foodItem, + refCode: '', servingSizes: convertServingSizes(foodItem), servingUnits: convertServingUnits(foodItem), nutrients: nutrientsFromFoodItem(foodItem), @@ -172,7 +172,6 @@ export function convertPassioIDAttributesToFoodItem( return { ...convertPassioFoodItem(passioIDAttributes.foodItem), name: passioIDAttributes.name, - imageName: passioIDAttributes.imageName, }; } else { const servingUnits = [ @@ -193,9 +192,8 @@ export function convertPassioIDAttributesToFoodItem( ), }, entityType: passioIDAttributes.entityType, - passioID: passioIDAttributes.passioID, name: passioIDAttributes.name, - imageName: passioIDAttributes.imageName, + refCode: '', nutrients: nutrientsFromFoodItem(passioIDAttributes.recipe), selectedUnit: selectedUnit, selectedQuantity: selectedQuantity, @@ -275,7 +273,7 @@ export function createFoodLogFromRecipe( recipe: Recipe, meal: MealLabel, date: Date, - passioID?: PassioID + _passioID?: PassioID ): FoodLog { const uuid: string = uuid4.v4() as string; return { @@ -285,8 +283,6 @@ export function createFoodLogFromRecipe( entityType: ENTITY_TYPE_RECIPE_PREFIX, uuid: uuid, name: recipe.name, - imageName: 'passioSDKNutritionIcon', - passioID: passioID ?? `${ENTITY_TYPE_RECIPE_PREFIX}-${uuid}`, selectedUnit: 'serving', selectedQuantity: 1, servingUnits: [ diff --git a/src/utils/QuickResultUtils.ts b/src/utils/QuickResultUtils.ts index 82b0a91..86ac745 100644 --- a/src/utils/QuickResultUtils.ts +++ b/src/utils/QuickResultUtils.ts @@ -21,7 +21,14 @@ export const getBarcodeResult = async (barcodeCandidate?: BarcodeCandidate) => { }; return attribute; } else { - return null; + const attribute: QuickResult = { + passioID: '', + name: barcodeCandidate.barcode, + type: 'Barcode', + barcode: barcodeCandidate.barcode, + passioIDAttributes: null, + }; + return attribute; } } else { return null; diff --git a/src/utils/V3Utils.tsx b/src/utils/V3Utils.tsx index d246f61..91579e0 100644 --- a/src/utils/V3Utils.tsx +++ b/src/utils/V3Utils.tsx @@ -19,6 +19,7 @@ import { convertDateToDBFormat } from './DateFormatter'; import { getMealLog } from '../utils'; import type { PassioIngredient } from '@passiolife/nutritionai-react-native-sdk-v3/src'; import type { QuickSuggestion } from 'src/models/QuickSuggestion'; +import type { DefaultNutrients } from '../screens/foodCreator/views/OtherNutritionFacts'; export const convertPassioFoodItemToFoodLog = ( item: PassioFoodItem, @@ -56,12 +57,10 @@ export const convertPassioFoodItemToFoodLog = ( name: foodItem.name, uuid: uuid, longName: foodItem.details, - passioID: foodItem.refCode ?? foodItem.id, refCode: foodItem.refCode, eventTimestamp: dateFormat, isOpenFood: foodItem.isOpenFood, meal: meal, - imageName: foodItem.iconId, iconID: foodItem.iconId, entityType: PassioIDEntityType.item, foodItems: newFoodIngredient, @@ -112,11 +111,12 @@ function convertPassioIngredientToFoodItem(item: PassioIngredient): FoodItem { passioIngredient.amount.weight ); const foodItem: FoodItem = { - passioID: passioIngredient.refCode ?? passioIngredient.id, name: passioIngredient.name, - imageName: passioIngredient.id, + refCode: passioIngredient.refCode ?? '', iconId: passioIngredient.iconId, entityType: PassioIDEntityType.item, + barcode: passioIngredient?.metadata?.barcode, + ingredientsDescription: passioIngredient?.metadata?.ingredientsDescription, computedWeight: { unit: passioIngredient.amount.weight.unit, value: passioIngredient.amount.weight.value, @@ -239,3 +239,53 @@ export const passioSuggestedFoods = async ( }) ); }; + +export const macroNutrientPercentages = ( + carbsG?: number, + fatG?: number, + proteinG?: number +) => { + // Calculate calories contributed by each macro nutrient + let carbsContributeOfCalories = (carbsG ?? 0) * 4; + let fatContributeOfCalories = (fatG ?? 0) * 9; + let proteinContributeOfCalories = (proteinG ?? 0) * 4; + + // Calculate total calories from macro nutrients + let totalMacroNutrientCalories = + carbsContributeOfCalories + + fatContributeOfCalories + + proteinContributeOfCalories; + + // Calculate percentages + let carbsPercentage = + (carbsContributeOfCalories / totalMacroNutrientCalories) * 100; + let fatPercentage = + (fatContributeOfCalories / totalMacroNutrientCalories) * 100; + let proteinPercentage = + (proteinContributeOfCalories / totalMacroNutrientCalories) * 100; + + return { + carbsPercentage: isNaN(carbsPercentage) ? 0 : carbsPercentage, + fatPercentage: isNaN(fatPercentage) ? 0 : fatPercentage, + proteinPercentage: isNaN(proteinPercentage) ? 0 : proteinPercentage, + }; +}; + +export function sumNutrients( + nutrients: DefaultNutrients[] +): DefaultNutrients[] { + const nutrientMap = nutrients.reduce( + (acc, nutrient) => { + if (nutrient.value !== undefined) { + acc[nutrient.label] = (acc[nutrient.label] || 0) + nutrient.value; + } + return acc; + }, + {} as Record + ); + + return Object.keys(nutrientMap).map((label) => ({ + label: label as NutrientType, + value: nutrientMap[label as NutrientType], + })); +} diff --git a/src/utils/passioFoodDataInfoUtils.ts b/src/utils/passioFoodDataInfoUtils.ts index f798a0c..fb9b779 100644 --- a/src/utils/passioFoodDataInfoUtils.ts +++ b/src/utils/passioFoodDataInfoUtils.ts @@ -3,7 +3,7 @@ import { PassioFoodItem, PassioSDK, } from '@passiolife/nutritionai-react-native-sdk-v3'; -import type { FoodLog, MealLabel } from '..'; +import type { FoodLog, MealLabel, ServingUnit } from '..'; import { getLogToDate, mealLabelByDate } from './ScaningUtils'; import { convertPassioFoodItemToFoodLog, @@ -45,26 +45,92 @@ export const createFoodLogUsingPortionSize = ( weightGram: number, portionSize: string ) => { - const [qty, unit] = portionSize.split(' '); + const array = portionSize.split(' '); - const isNotIncludedUnit = + let qty = 1; + let unit = ''; + + const value = array[0]; + + if (array.length > 1) { + if (/^\d+$/.test(value)) { + qty = Number(value); + unit = array.slice(1, array.length).join(''); + } else { + unit = array.slice(0, array.length).join(''); + } + } else { + unit = array[0]; + } + + let foodLog = convertPassioFoodItemToFoodLog(foodItem, logToDate, meal); + + let isUnitNotIncludeInServingUnits = foodItem?.amount.servingUnits?.filter( (i) => i.unitName?.toString() === unit?.toString() ).length === 0; - let foodLog = convertPassioFoodItemToFoodLog(foodItem, logToDate, meal); + let isDefaultSelectedUnitNotSame = foodLog.selectedUnit !== unit; + + let isUnitGram = + qty > 0 && + (unit.toLowerCase() === 'g' || + unit.toLowerCase() === 'gram' || + unit.toLowerCase() === 'ml' || + unit.toLowerCase() === 'grams'); + + // isUnitGram ? 1 : qty, + if (isUnitNotIncludeInServingUnits || isDefaultSelectedUnitNotSame) { + foodLog = recalculatedFoodLogPerQtyUnitWeight( + foodLog, + isUnitGram ? 1 : 1, + 'gram', + weightGram + ); + } - if (isNotIncludedUnit) { + // It's include but not default selected + if (!isUnitNotIncludeInServingUnits && isDefaultSelectedUnitNotSame) { + foodLog = updateFoodLogByServingUnits( + foodLog, + foodLog.servingUnits.find((i) => i.unit === unit) + ); + } + + return foodLog; +}; + +export const recalculatedFoodLogPerQtyUnitWeight = ( + foodLog: FoodLog, + qty: number, + unit: string, + weighGram: number +) => { + const { computedWeight, foodItems } = foodLog; + const servingWeight = + computedWeight?.value ?? foodItems[0]?.computedWeight.value; + const defaultWeight = servingWeight ?? 0; + const newQuantity = Number(defaultWeight / Number(qty)); + foodLog.selectedQuantity = Number(newQuantity); + foodLog.selectedUnit = unit; + foodLog = updateQuantityOfFoodLog(foodLog, weighGram); + return foodLog; +}; + +export const updateFoodLogByServingUnits = ( + foodLog: FoodLog, + servingUnit?: ServingUnit +) => { + if (servingUnit) { const { computedWeight, foodItems } = foodLog; const servingWeight = computedWeight?.value ?? foodItems[0]?.computedWeight.value; const defaultWeight = servingWeight ?? 0; - const newQuantity = Number(defaultWeight / Number(qty)); + const newQuantity = Number(defaultWeight / servingUnit.mass); foodLog.selectedQuantity = Number( newQuantity < 10 ? newQuantity.toFixed(2) : Math.round(newQuantity) ); - foodLog.selectedUnit = 'gram'; - foodLog = updateQuantityOfFoodLog(foodLog, weightGram); + foodLog.selectedUnit = servingUnit.unit; return foodLog; } else { return foodLog; @@ -86,3 +152,17 @@ export const getUpdatedCaloriesOfPassioAdvisorFoodInfo = ( advisorInfoWeightGram: advisorInfoWeightGram, }; }; + +export function extractNumberAndString(input: string) { + const regex = /(\d+)\s*(.*)/; + const match = input.match(regex); + + if (match) { + return { + number: parseInt(match[1], 10), + otherString: match[2], + }; + } else { + return null; + } +} diff --git a/src/utils/quickSuggestionUtils.ts b/src/utils/quickSuggestionUtils.ts index 974e55b..c2e4d68 100644 --- a/src/utils/quickSuggestionUtils.ts +++ b/src/utils/quickSuggestionUtils.ts @@ -83,10 +83,10 @@ export function orderFrequencySort(items: FoodLog[]): QuickSuggestion[] { const find = items.find((i) => i.name === itemName); if (find) { result.push({ - refCode: find.refCode ?? find.passioID, + refCode: find.refCode ?? find.refCode, foodLog: find, foodName: find.name, - iconID: find.imageName, + iconID: find.iconID ?? '', }); } addedItems.add(itemName); diff --git a/yarn.lock b/yarn.lock index 8b9708f..c38c248 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2127,10 +2127,10 @@ resolved "https://registry.yarnpkg.com/@react-native-community/slider/-/slider-4.5.0.tgz#5c55488ee30060cd87100fb746b9d8655dbab04e" integrity sha512-pyUvNTvu5IfCI5abzqRfO/dd3A009RC66RXZE6t0gyOwI/j0QDlq9VZRv3rjkpuIvNTnsYj+m5BHlh0DkSYUyA== -"@react-native-picker/picker@^2.6.1": - version "2.7.2" - resolved "https://registry.yarnpkg.com/@react-native-picker/picker/-/picker-2.7.2.tgz#c91c6b936d5b9872190f56836bdba227eb521571" - integrity sha512-a+UiyCu0XtXJK+VGIFggwA02PU3dgnvUPxlk2u99ZphTFp80Va8WH1OM9g2gyUXTVg9a5iybpDBa2+7fDfIMXA== +"@react-native-picker/picker@^2.7.7": + version "2.7.7" + resolved "https://registry.yarnpkg.com/@react-native-picker/picker/-/picker-2.7.7.tgz#6e19c3a72a482be015f5194794e6c14efe8762e8" + integrity sha512-CTHthVmx8ujlH/u5AnxLQfsheh/DoEbo+Kbx0HGTlbKVLC1eZ4Kr9jXIIUcwB7JEgOXifdZIPQCsoTc/7GQ0ag== "@react-native-voice/voice@^3.2.4": version "3.2.4"