Integrate Food Detail

 /**
   * Look up the nutrition attributes for a given Passio ID.
   * @param passioID - The Passio ID for the attributes query.
   * @returns A `Promise` resolving to a `PassioFoodItem` object if the record exists in the database or `null` if not.
   */
  getAttributesForPassioID(passioID: PassioID): Promise<PassioFoodItem | null>
/**
   * Query Passio's UPC web service for nutrition attributes of a given barcode.
   * @param barcode - The barcode value for the attributes query, typically taken from a scanned `BarcodeCandidate`.
   * @returns A `Promise` resolving to a `PassioFoodItem` object if the record exists in the database or `null` if not.
   */
  fetchAttributesForBarcode(barcode: Barcode): Promise<PassioFoodItem | null>

  /**
   * Query Passio's web service for nutrition attributes given an package food identifier.
   * @param packagedFoodCode - The code identifier for the attributes query, taken from the list of package food candidates on a `FoodDetectionEvent`.
   * @returns A `Promise` resolving to a `PassioFoodItem` object if the record exists in the database or `null` if not.
   */
  fetchPassioIDAttributesForPackagedFood(
    packagedFoodCode: PackagedFoodCode
  ): Promise<PassioFoodItem | null>

Example

import { useCallback, useState } from 'react'
import type {
  PassioFoodItem,
  PassioNutrients,
  ServingUnit,
} from '@passiolife/nutritionai-react-native-sdk-v3'

interface Props {
  passioFoodItem: PassioFoodItem
}

export interface FoodNutrient {
  title: string
  value: number
  unit: string
}

export interface ComputedWeight {
  value: number
  qty: string
}

export const useFoodDetail = ({ passioFoodItem }: Props) => {
  const defaultServing = {
    unitName: passioFoodItem?.amount?.selectedUnit,
    value: passioFoodItem?.amount?.weight?.value ?? 0,
  }
  const defaultCalculatedWeight = passioFoodItem?.amount?.weight?.value ?? 0

  const [textInputServingQty, setTextInputServingQty] = useState<string>(
    passioFoodItem?.amount?.selectedQuantity.toString() ?? ''
  )
  const [calculatedWeight, setCalculatedWeight] = useState<number>(
    defaultCalculatedWeight
  )
  const [selectedServingUnit, setSelectedServingUnit] =
    useState<ServingUnit>(defaultServing)

  const onServingQuantityChange = useCallback(
    (value: string) => {
      const newQuantity = Number(value)
      setTextInputServingQty(value)
      if (newQuantity > 0) {
        setCalculatedWeight((selectedServingUnit.value ?? 0) * newQuantity)
      }
    },
    [selectedServingUnit.value]
  )

  const onServingSizeSelect = useCallback(
    (value: ServingUnit) => {
      const defaultWeight = calculatedWeight ?? 1
      const newQuantity = Number((defaultWeight / value.value).toFixed(2))
      setSelectedServingUnit(value)
      setTextInputServingQty(newQuantity.toString())
    },
    [calculatedWeight]
  )

  function extractFoodNutrients(
    passioNutrients: PassioNutrients
  ): FoodNutrient[] {
    return Object.entries(passioNutrients).map(([title, { value, unit }]) => ({
      title,
      value: value,
      unit,
    }))
  }

  return {
    calculatedWeight,
    calculatedWeightUnit: passioFoodItem?.amount?.weight?.unit,
    selectedServingUnit,
    extractFoodNutrients,
    onServingQuantityChange,
    onServingSizeSelect,
    passioFoodItem,
    textInputServingQty,
  }
}
import React from 'react'
import {
  FlatList,
  SafeAreaView,
  ScrollView,
  StyleSheet,
  Text,
  TextInput,
  TouchableOpacity,
  Image,
  View,
  Pressable,
} from 'react-native'
import { useFoodDetail } from './useFoodDetail'
import {
  PassioIconView,
  IconSize,
  PassioFoodItem,
  PassioFoodItemNutrients,
} from '@passiolife/nutritionai-react-native-sdk-v3'

interface Props {
  onClose: () => void
  passioFoodItem: PassioFoodItem
}

const FoodDetail = ({ onClose, passioFoodItem }: Props) => {
  const styles = foodDetailStyle()
  const {
    calculatedWeight,
    calculatedWeightUnit,
    selectedServingUnit,
    extractFoodNutrients,
    onServingQuantityChange,
    onServingSizeSelect,
    textInputServingQty,
  } = useFoodDetail({ passioFoodItem })

  console.log(passioFoodItem)

  const nutrients = extractFoodNutrients(
    new PassioFoodItemNutrients(passioFoodItem).nutrients({
      unit: calculatedWeightUnit ?? 'g',
      value: calculatedWeight,
    })
  )

  const renderNutrientItem = () => (
    <View style={styles.nutrientContainer}>
      <Text style={styles.title}>Nutrients</Text>
      <FlatList
        data={nutrients}
        renderItem={({ item }) => {
          return (
            <View style={styles.nutrientsContainer}>
              <Text style={styles.nutrientTitle}>{item.title}</Text>
              <Text>
                {item.value.toFixed(2)} {item.unit}
              </Text>
            </View>
          )
        }}
      />
    </View>
  )

  const renderEditServing = () => {
    return (
      <View style={styles.nutrientContainer}>
        <Text style={styles.title}>{`Serving Sizes (${calculatedWeight.toFixed(
          2
        )} ${calculatedWeightUnit})`}</Text>
        <TextInput
          value={textInputServingQty}
          style={styles.textInput}
          keyboardType="numeric"
          placeholder="Serving Size"
          placeholderTextColor={'gray'}
          onChangeText={onServingQuantityChange}
        />
        <FlatList
          horizontal
          showsHorizontalScrollIndicator={false}
          data={passioFoodItem.amount.servingUnits}
          renderItem={({ item }) => (
            <Pressable
              onPress={() => onServingSizeSelect(item)}
              style={[
                styles.servingContainer,
                selectedServingUnit.unitName === item.unitName &&
                  styles.servingSelectedContainer,
              ]}
            >
              <Text
                style={[
                  styles.servingContainerTitle,
                  selectedServingUnit.unitName === item.unitName &&
                    styles.servingSelectedContainerTitle,
                ]}
              >
                {item.unitName} ({item.value})
              </Text>
            </Pressable>
          )}
        />
      </View>
    )
  }

  const renderIngredient = () => {
    return (
      <View style={styles.nutrientContainer}>
        <Text style={styles.title}>Ingredients</Text>
        <FlatList
          data={passioFoodItem.ingredients}
          renderItem={({ item }) => (
            <View style={styles.ingredientsContainer}>
              <View style={styles.itemIconContainer}>
                <PassioIconView
                  style={styles.itemIcon}
                  config={{
                    passioID: item.iconId,
                    iconSize: IconSize.PX360,
                  }}
                />
              </View>
              <View style={{ alignSelf: 'center' }}>
                <Text style={styles.ingredientTitle}>{item.name}</Text>
                <Text style={styles.ingredientDetail}>
                  {item.weight?.value} {item.weight?.unit},{' '}
                  {item.amount?.selectedQuantity} {item.amount?.selectedUnit}
                </Text>
              </View>
            </View>
          )}
        />
      </View>
    )
  }

  const renderMacro = () => {
    return (
      <View style={styles.macroContainer}>
        {['Calories', 'Carbs', 'Protein', 'Fat'].map((title) => {
          const nutrient = nutrients.find(
            (item) => item.title.toLowerCase() === title.toLowerCase()
          )
          return (
            <View key={title} style={{ flex: 1, marginTop: 12 }}>
              <Text style={{ fontSize: 16, fontWeight: '600' }}>{title}</Text>
              <Text>{(nutrient?.value ?? 0).toFixed(2)}</Text>
            </View>
          )
        })}
      </View>
    )
  }

  const renderFoodDetailCard = () => {
    return (
      <View style={styles.itemFoodInfoContainer}>
        <View style={styles.itemFoodInfo}>
          <View style={styles.itemIconContainer}>
            <PassioIconView
              style={styles.itemIcon}
              config={{
                passioID: passioFoodItem.iconId,
                iconSize: IconSize.PX360,
              }}
            />
          </View>
          <View style={styles.flex1}>
            <Text style={styles.foodName}>{passioFoodItem.name}</Text>
            <Text style={styles.foodDetail}>
              {calculatedWeight.toFixed(2)} {calculatedWeightUnit} |{' '}
              {selectedServingUnit.value.toFixed(2)}{' '}
              {selectedServingUnit.unitName}
            </Text>
            {passioFoodItem.isOpenFood && (
              <Text style={styles.foodDetail}>This item is Open food</Text>
            )}
          </View>
        </View>
        {renderMacro()}
      </View>
    )
  }

  return (
    <SafeAreaView style={styles.container}>
      <ScrollView>
        <View style={styles.closeButton}>
          <TouchableOpacity onPress={onClose}>
            <Image
              source={require('../assets/back.png')}
              style={styles.backIcon}
            />
          </TouchableOpacity>
        </View>
        {passioFoodItem && (
          <View style={styles.itemDetailContainer}>
            {renderFoodDetailCard()}
            <View style={styles.line} />
            {renderEditServing()}
            <View style={styles.line} />
            {renderNutrientItem()}
            <View style={styles.line} />
            {renderIngredient()}
            <View style={styles.line} />
            <View style={styles.line} />
            <View style={styles.line} />
            <View style={styles.line} />
          </View>
        )}
      </ScrollView>
    </SafeAreaView>
  )
}

const foodDetailStyle = () =>
  StyleSheet.create({
    container: {
      backgroundColor: 'rgba(242, 245, 251, 1)',
      flex: 1,
    },
    flex1: {
      flex: 1,
    },
    macroContainer: {
      flexDirection: 'row',
      flex: 1,
    },
    closeButton: {
      margin: 16,
    },
    itemFoodInfo: {
      flexDirection: 'row',
      alignSelf: 'center',
      justifyContent: 'center',
      flex: 1,
    },
    itemFoodInfoContainer: {
      borderRadius: 16,
      backgroundColor: 'white',
      padding: 16,
    },
    foodName: {
      paddingHorizontal: 16,
      fontSize: 16,
      textTransform: 'capitalize',
      fontWeight: '600',
    },
    foodDetail: {
      paddingHorizontal: 16,
    },
    nutrientContainer: {
      backgroundColor: 'white',
      padding: 16,
    },
    backIcon: {
      height: 24,
      width: 24,
    },
    nutrientsContainer: {
      flexDirection: 'row',
      marginVertical: 4,
    },
    ingredientsContainer: {
      flexDirection: 'row',
      marginVertical: 4,
      padding: 8,
      backgroundColor: 'rgba(238, 242, 255, 1)',
    },
    ingredientTitle: {
      marginHorizontal: 16,
      fontWeight: '500',
      fontSize: 14,
    },
    ingredientDetail: {
      marginHorizontal: 16,
    },
    line: {
      height: 0.5,
      marginVertical: 8,
      backgroundColor: 'white',
    },
    title: {
      fontWeight: '500',
      color: 'black',
      fontSize: 16,
      marginBottom: 12,
      flex: 1,
    },
    servingContainerTitle: {
      fontWeight: '400',
      color: 'black',
      fontSize: 13,
      paddingVertical: 6,
      overflow: 'hidden',
      textTransform: 'capitalize',
    },
    nutrientTitle: {
      fontWeight: '400',
      color: 'black',
      textTransform: 'capitalize',
      flex: 1,
    },
    servingContainer: {
      marginHorizontal: 4,
      borderRadius: 12,
      paddingHorizontal: 8,
      backgroundColor: 'rgba(238, 242, 255, 1)',
    },
    servingSelectedContainer: {
      backgroundColor: 'blue',
    },
    servingSelectedContainerTitle: {
      color: 'white',
    },
    itemContainer: {
      padding: 12,
      backgroundColor: 'white',
      marginVertical: 4,
      marginHorizontal: 16,
      flexDirection: 'row',
      alignItems: 'center',
    },
    itemFoodName: {
      flex: 1,
      textTransform: 'capitalize',
      marginHorizontal: 8,
      overflow: 'hidden',
      fontSize: 16,
    },
    itemDetailContainer: {
      padding: 16,
    },
    itemDetail: {
      color: 'white',
    },
    itemIconContainer: {
      overflow: 'hidden',
      height: 60,
      width: 60,
      borderRadius: 30,
    },
    itemIcon: {
      height: 60,
      width: 60,
    },
    textInput: {
      borderColor: 'rgba(209, 213, 219, 1)',
      backgroundColor: 'rgba(238, 242, 255, 1)',
      borderWidth: 1,
      paddingHorizontal: 16,
      borderRadius: 16,
      padding: 16,
      marginVertical: 16,
    },
  })

export default FoodDetail

Last updated