Skip to content

Latest commit

 

History

History
594 lines (481 loc) · 17.2 KB

File metadata and controls

594 lines (481 loc) · 17.2 KB

React Native Passio Nutrition-AI UI/UX SDK

Installation

Step 1: Create an .npmrc or yarnrc.yml file in the root of your project with the following lines replacing GITHUB_ACCESS_TOKEN with the token you've created

.npmrc

//npm.pkg.github.com/:_authToken=GITHUB_ACCESS_TOKEN
@passiolife:registry=https://npm.pkg.github.com

or yarnrc.yml

npmScopes:
  passiolife:
    npmRegistryServer: 'https://npm.pkg.github.com'
    npmAuthToken: 'GITHUB_ACCESS_TOKEN'

Step 2: Open terminal

yarn add @passiolife/nutrition-ai-ui-ux

yarn add @passiolife/nutritionai-react-native-sdk-v3

Step 3: add react-native.config.js at root

    project: {
      ios: {},
      android: {},
    },
    dependencies: {
      'react-native-linear-gradient': {},
      '@react-native-async-storage/async-storage': {},
      '@react-native-community/datetimepicker': {},
      'react-native-fs': {},
      '@notifee/react-native': {},
      'react-native-svg': {},
      '@passiolife/nutritionai-react-native-sdk-v3': {},
      'react-native-vision-camera': {},
      'react-native-image-picker': {},
      '@react-native-community/slider': {},
      'lottie-react-native': {},
      '@react-native-voice/voice': {},
    },
  };

Step 4: For Android, add this implementation line to the dependencies section on app/build.gradle file

dependencies {
    // Add this line below for Passio SDK library
    implementation files("$rootDir/../node_modules/@passiolife/nutritionai-react-native-sdk-v3/android/libs/passiolib-release.aar")
    ...
}

Make sure this dependencies are added into your project

yarn add @react-navigation/native
yarn add @react-navigation/native-stack
yarn add @react-navigation/stack
yarn add react-native-gesture-handler
yarn add react-native-reanimated
yarn add react-native-safe-area-context
yarn add react-native-screens
yarn add react-native-sqlite-storage

Add Reanimated's babel plugin

Add react-native-reanimated/plugin plugin to your babel.config.js.

module.exports = {
    presets: [
      ... // don't add it here :)
    ],
    plugins: [
      ...
      'react-native-reanimated/plugin',
    ],
  };

Permission

IOS Permission

 Privacy - NSCameraUsageDescription
 Privacy - NSSpeechRecognitionUsageDescription
 Privacy - NSMicrophoneUsageDescription
 Privacy - Photo Library Usage Description

Android Permission

<uses-permission android:name="android.permission.CAMERA" />

Required Dependencies

Dependency Required Version
react-native-reanimated >=^3.6.1
react-native-gesture-handler >=2.16.0
react-native-safe-area-context >=4.8.2

⚠️ Issue

If you find a duplicate entry for '@react-navigation', ensure that the your project navigation dependencies are match our navigation dependencies require versions.

Additional Navigation Dependencies

Dependency Required Version
@react-navigation/native >=^6.1.17
@react-navigation/native-stack >=6.1.17
@react-navigation/stack >=6.3.29
@react-navigation/bottom-tabs >=6.5.20
react-native-screens >=3.30.1

Usage example

Note: Ensure your SDK is configured correctly before launching the Nutrition AI module

Using Internal Services

import React from 'react';
import {
  BrandingProvider,
  NutritionNavigator,
  ServicesProvider,
  usePassioConfig
} from '@passiolife/nutrition-ai-ui-ux';
import { NavigationContainer } from '@react-navigation/native';

export default function App() {

 const { isReady } = usePassioConfig({ key: "YOUR_PASSIO_KEY" });

  if (!isReady) {
    return <Loading />;
  }

  return (
    <ServicesProvider>
      <BrandingProvider>
          <NutritionNavigator />
      </BrandingProvider>
    </ServicesProvider>
  );
}

Using External Services

Step 1: If you'd like to include your data service, follow the steps below. Otherwise, skip to the next step

NutritionDataService used for Return a function to store or retrieve data through a REST API, local database, or Firebase, etc

export const dataService: NutritionDataService = {
  /**
   * Retrieves the patient profile.
   * @returns A `Promise` resolving to the patient profile.
   */
  getPatientProfile: () => getPatientProfile(),

  /**
   * Saves a food log.
   * @param foodLog - The food log to save.
   * @returns A `Promise` that resolves when the operation is complete.
   */
  async saveFoodLog(foodLog: FoodLog): Promise<void> {
    return saveFoodLog(foodLog);
  },

  /**
   * Retrieves food logs.
   * @returns A `Promise` resolving to an array of food logs.
   */
  async getFoodLogs(): Promise<FoodLog[]> {
    return getFoodLogs();
  },

  /**
   * Deletes a food log.
   * @param uuid - The UUID of the food log to delete.
   * @returns A `Promise` that resolves when the operation is complete.
   */
  async deleteFoodLog(uuid: string): Promise<void> {
    return deleteFoodLog(uuid);
  },

  /**
   * Deletes a recipe.
   * @param uuid - The UUID of the recipe to delete.
   * @returns A `Promise` that resolves when the operation is complete.
   */
  async deleteRecipe(uuid: string): Promise<void> {
    return deleteRecipe(uuid);
  },

  /**
   * Deletes a favorite food item.
   * @param uuid - The UUID of the favorite food item to delete.
   * @returns A `Promise` that resolves when the operation is complete.
   */
  async deleteFavoriteFoodItem(uuid: string): Promise<void> {
    return deleteFavoriteFoodItem(uuid);
  },

  /**
   * Retrieves meal logs within a specified date range.
   * @param startDate - The start date of the range.
   * @param endDate - The end date of the range.
   * @returns A `Promise` resolving to an array of meal logs.
   */
  async getMealLogs(startDate: Date, endDate: Date): Promise<FoodLog[]> {
    return getMealLogs(startDate, endDate);
  },

  /**
   * Saves a favorite food item.
   * @param favoriteFoodItem - The favorite food item to save.
   * @returns A `Promise` that resolves when the operation is complete.
   */
  async saveFavoriteFoodItem(favoriteFoodItem: FavoriteFoodItem): Promise<void> {
    return saveFavoriteFoodItem(favoriteFoodItem);
  },

  /**
   * Retrieves favorite food items.
   * @returns A `Promise` resolving to an array of favorite food items.
   */
  async getFavoriteFoodItems(): Promise<FavoriteFoodItem[]> {
    return getFavoriteFoodItems();
  },

  /**
   * Saves a nutrition profile.
   * @param nutritionProfile - The nutrition profile to save.
   * @returns A `Promise` that resolves when the operation is complete.
   */
  saveNutritionProfile(nutritionProfile: NutritionProfile): Promise<void> {
    return saveNutritionProfile(nutritionProfile);
  },

  /**
   * Saves a recipe.
   * @param recipe - The recipe to save.
   * @returns A `Promise` that resolves when the operation is complete.
   */
  async saveRecipe(recipe: Recipe): Promise<void> {
    return saveRecipe(recipe);
  },

  /**
   * Retrieves recipes.
   * @returns A `Promise` resolving to an array of recipes.
   */
  async getRecipes(): Promise<Recipe[]> {
    return getRecipes();
  },

  /**
   * Retrieves the nutrition profile.
   * @returns A `Promise` resolving to the nutrition profile, or `undefined` if not found.
   */
  async getNutritionProfile(): Promise<NutritionProfile | undefined> {
    return getNutritionProfile();
  },

  /**
   * Retrieves water logs within a specified date range.
   * @param startDate - The start date of the range.
   * @param endDate - The end date of the range.
   * @returns A `Promise` resolving to an array of water logs.
   */
  async getWaters(startDate: Date, endDate: Date): Promise<Water[]> {
    return getWaters(startDate, endDate);
  },

  /**
   * Deletes a water log.
   * @param uuid - The UUID of the water log to delete.
   * @returns A `Promise` that resolves when the operation is complete.
   */
  async deleteWater(uuid: string): Promise<void> {
    return deleteWater(uuid);
  },

  /**
   * Deletes a weight log.
   * @param uuid - The UUID of the weight log to delete.
   * @returns A `Promise` that resolves when the operation is complete.
   */
  async deleteWeight(uuid: string): Promise<void> {
    return deleteWeight(uuid);
  },

  /**
   * Retrieves weight logs within a specified date range.
   * @param startDate - The start date of the range.
   * @param endDate - The end date of the range.
   * @returns A `Promise` resolving to an array of weight logs.
   */
  async getWeight(startDate: Date, endDate: Date): Promise<Weight[]> {
    return getWeight(startDate, endDate);
  },
};

Step: 2 If you're want to apply your theme, you can use the following, although it's in experimental mode. otherwise you can skip to next step

Nutrition-UX SDK also provide Branding into BrandingProvider.

  // you can change primary color form here
 export const branding: Branding = {
  primaryColor: 'rgba(79, 70, 229, 1)',
  backgroundColor: 'rgba(249, 250, 251, 1)',
  black: 'rgba(0, 0, 0, 1)',
  text: 'rgba(17, 24, 39, 1)',
  border: 'rgba(229, 231, 235, 1)',
  calories: 'rgba(245, 158, 11, 1)',
  carbs: 'rgba(14, 165, 233, 1)',
  error: 'rgba(239, 68, 68, 1)',
  fat: 'rgba(139, 92, 246, 1)',
  font: 'Passio-Regular',
  gray300: 'rgba(209, 213, 219, 1)',
  gray500: 'rgba(107, 114, 128, 1)',
  indigo50: 'rgba(238, 242, 255, 1)',
  proteins: 'rgba(16, 185, 129, 1)',
  purple: 'rgba(79, 70, 229, 1)',
  searchBody: 'rgba(242, 245, 251, 1)',
  secondaryText: 'rgba(107, 114, 128, 1)',
  white: 'white',
  };

Step:3 : If you're want to check some log event then provide analytic service, although it's in experimental mode

  export  const analyticsService: AnalyticsService = {
    logEvent(event: string) {
      console.log(`Analytics: ${event}`); // eslint-disable-line no-console
    },
  };
import React from 'react';
import {
  BrandingProvider,
  NutritionNavigator,
  ServicesProvider,
  usePassioConfig
} from '@passiolife/nutrition-ai-ui-ux';
import { NavigationContainer } from '@react-navigation/native';

export default function App() {

  const services: Services = {
    dataService,
    analyticsService,
  };


const { isReady } = usePassioConfig({ key: "YOUR_PASSIO_KEY" });

  if (!isReady) {
    return <Loading />;
  }

  return (
    <ServicesProvider services={services}>
      <BrandingProvider branding={branding}>
        <NavigationContainer>
          <NutritionNavigator />
        </NavigationContainer>
      </BrandingProvider>
    </ServicesProvider>
  );
}

Use PassioScreens as Stack without navigation container

import { createNativeStackNavigator } from '@react-navigation/native-stack';
import React from 'react';
import { enableScreens } from 'react-native-screens';
import { NavigationContainer } from '@react-navigation/native';
import { PassioScreens } from '@passiolife/nutrition-ai-ui-ux';

const Stack = createNativeStackNavigator();
enableScreens();

export const AppNavigator = () => {

  return (
    <>
        <NavigationContainer>
          <Stack.Navigator
            screenOptions={{ gestureEnabled: false, animation: 'simple_push' }}
            initialRouteName={'PassioScreens'}
            <Stack.Screen
              options={{ headerShown: false }}
              name={'PassioScreens'}
              component={PassioScreens}
            />
          </Stack.Navigator>
        </NavigationContainer>
    </>
  );
};

NutritionDataService callback functions

Callback Argument Return Description
saveNutritionProfile NutritionProfile void This function provides you NutritionProfile object for save nutrition profile
saveFoodLog FoodLog void This function provide you FoodLog for save food log
saveFavoriteFoodItem FavoriteFoodItem void This function provides you FavoriteFoodItem object for save favortie food item
saveRecipe Recipe void This function provides you Recipe object for save recipe
deleteRecipe uuID void This function provide you delete recipe uuid for delete recipe
deleteFoodLog uuID void This function provide you delete foodLog uuid for delete food log
getNutritionProfile - NutritionProfile or undefined You have to provide NutritionProfile or undefined to this funciton
getFoodLogs - FoodLog You have to provide FoodLog to this funciton
getFavoriteFoodItems - FavoriteFoodItem[] You have to provide FavoriteFoodItem[] to this funciton
getMealLogs startDate: Date, endDate: Date FoodLog[] You have to provide FoodLog[] between this start data and end date to this funciton
getPatientProfile void PatientProfile You have to provide PatientProfile to this funciton
getRecipes void Recipe[] You have to provide Recipe[] to this funciton

Contributing

To begin development, clone the project and check out the develop branch.

Create a new branch from develop for your assigned ticket with the format feature/my-ticket-#5 where my-ticket is a few words describing the feature and #5 is the Github issue number. Please make sure you have moved the ticket to the "In Progress" column in Github.

As you develop your feature, run the example app to test and debug your code.

Once your work is complete, verify that you have met all acceptance criteria on the ticket and have sufficient tests to cover the behavior. Then you may create a pull request back to the develop branch which will be reviewed and subsequently approved and merged.

⚠️ Issue

If your project not runnable in IOS then follow below steps

  • removed podfile.lock
  • remove pods
  • remove node_modules in example
  • remove node_modules in root
  • remove derived data
  • remove library cache
  • restart system
  • yarn at root
  • open xcode

If you find a duplicate entry for '@react-navigation', ensure that the navigation dependencies are listed above the other '@react-navigation' dependencies

To resolve the issue, you can use the following resolutions in your package.json:

"resolutions": {
    "@types/react": "18.0.12",
    "react-native": "0.68.1",
    "react-native-reanimated": "3.6.1",
    "@react-native-community/slider": "4.5.0",
    "react-native-safe-area-context": "4.8.2",
    ...others
  }

⚠️ Issue Voice logging

Notes on Android

Even after all the permissions are correct in Android, there is one last thing to make sure this libray is working fine on Android. Please make sure the device has Google Speech Recognizing Engine such as com.google.android.googlequicksearchbox by calling Voice.getSpeechRecognitionServices(). Since Android phones can be configured with so many options, even if a device has googlequicksearchbox engine, it could be configured to use other services. You can check which serivce is used for Voice Assistive App in following steps for most Android phones:

Settings > App Management > Default App > Assistive App and Voice Input > Assistive App

Above flow can vary depending on the Android models and manufactures. For Huawei phones, there might be a chance that the device cannot install Google Services.

How can I get com.google.android.googlequicksearchbox in the device?

Please ask users to install Google Search App.

⚠️ Issue @react-native-async-storage/async-storage

If you pod or build not sync please add @react-native-async-storage/async-storage

yarn add  @react-native-async-storage/async-storage

Reference Properties

export interface FoodLog extends ServingInfo {
  name: string;
  uuid: string;
  passioID: PassioID;
  refCode?: string;
  eventTimestamp: string;
  isOpenFood?: boolean;
  longName?: string;
  meal: MealLabel;
  imageName: string;
  entityType: PassioIDEntityType | 'user-recipe';
  foodItems: FoodItem[];
}
export interface ServingInfo {
  selectedUnit: string;
  selectedQuantity: number;
  servingSizes: ServingSize[];
  servingUnits: ServingUnit[];
  computedWeight?: ComputedWeight;
}
export interface FoodItem extends ServingInfo {
  passioID: PassioID;
  name: string;
  imageName: string;
  entityType: PassioIDEntityType;
  computedWeight: ComputedWeight;
  ingredientsDescription?: string;
  barcode?: string;
  nutrients: Nutrient[];
}
export interface Nutrient {
  id: NutrientType;
  amount: number;
  unit: string;
}
export interface NutritionProfile {
  caloriesTarget: number;
  carbsPercentage: number;
  proteinPercentage: number;
  fatPercentage: number;
  unitLength: UnitSystem;
  unitsWeight: UnitSystem;
  gender: 'male' | 'female';
  height: number;
  age: number;
  weight: number;
  activityLevel: ActivityLevelType;
  diet?: DietType;
  name: string;
  caloriesDeficit?: CaloriesDeficit;
  targetWater?: number;
  targetWeight?: number;
  breakFastNotification?: boolean;
  dinnerNotification?: boolean;
  lunchNotification?: boolean;
  mealPlan?: string;
}