feat: Implement Food Camera Screen and Floating Food Overlay
- Added FoodCameraScreen for capturing food images with camera functionality. - Integrated image picker for selecting images from the gallery. - Created FloatingFoodOverlay for quick access to food library and scanning options. - Updated NutritionRadarCard to utilize FloatingFoodOverlay for adding food. - Enhanced ExploreScreen layout and styles for better user experience. - Removed unused SafeAreaView from ExploreScreen. - Updated profile edit screen to remove unnecessary state variables. - Updated avatar image source in profile edit screen. - Added ExpoCamera dependency for camera functionalities.
This commit is contained in:
@@ -34,7 +34,6 @@ import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
AppState,
|
||||
Image,
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
@@ -366,7 +365,7 @@ export default function ExploreScreen() {
|
||||
|
||||
// 使用 lodash debounce 防抖的加载所有数据方法
|
||||
const debouncedLoadAllData = React.useMemo(
|
||||
() => debounce(executeLoadAllData, 300), // 300ms 防抖延迟
|
||||
() => debounce(executeLoadAllData, 500), // 500ms 防抖延迟
|
||||
[executeLoadAllData]
|
||||
);
|
||||
|
||||
@@ -573,7 +572,7 @@ export default function ExploreScreen() {
|
||||
<View style={styles.container}>
|
||||
{/* 背景渐变 */}
|
||||
<LinearGradient
|
||||
colors={['#f8f3faff', '#b3f6f8ff', '#bcf1f7ff', '#d3edf8ff']}
|
||||
colors={['#f5e5fbff', '#e5fcfeff', '#eefdffff', '#e6f6fcff']}
|
||||
style={styles.gradientBackground}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 0, y: 1 }}
|
||||
@@ -583,157 +582,159 @@ export default function ExploreScreen() {
|
||||
<View style={styles.decorativeCircle1} />
|
||||
<View style={styles.decorativeCircle2} />
|
||||
|
||||
<SafeAreaView style={styles.safeArea}>
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
contentContainerStyle={{ paddingBottom: bottomPadding }}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{/* 顶部信息栏 */}
|
||||
<View style={styles.headerContainer}>
|
||||
<View style={styles.headerContent}>
|
||||
{/* 左边logo */}
|
||||
<Image
|
||||
source={require('@/assets/images/Sealife.jpeg')}
|
||||
style={styles.logoImage}
|
||||
resizeMode="cover"
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
contentContainerStyle={{
|
||||
paddingTop: insets.top,
|
||||
paddingBottom: bottomPadding,
|
||||
paddingHorizontal: 20
|
||||
}}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{/* 顶部信息栏 */}
|
||||
<View style={styles.headerContainer}>
|
||||
<View style={styles.headerContent}>
|
||||
{/* 左边logo */}
|
||||
<Image
|
||||
source={require('@/assets/images/Sealife.jpeg')}
|
||||
style={styles.logoImage}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
|
||||
{/* 右边文字区域 */}
|
||||
<View style={styles.headerTextContainer}>
|
||||
<Text style={styles.headerTitle}>海豹健康</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
|
||||
|
||||
{/* 日期选择器 */}
|
||||
<DateSelector
|
||||
selectedIndex={selectedIndex}
|
||||
onDateSelect={onSelectDate}
|
||||
showMonthTitle={false}
|
||||
disableFutureDates={true}
|
||||
/>
|
||||
|
||||
|
||||
|
||||
{/* 营养摄入雷达图卡片 */}
|
||||
<NutritionRadarCard
|
||||
nutritionSummary={nutritionSummary}
|
||||
nutritionGoals={nutritionGoals}
|
||||
burnedCalories={(basalMetabolism || 0) + (activeCalories || 0)}
|
||||
basalMetabolism={basalMetabolism || 0}
|
||||
activeCalories={activeCalories || 0}
|
||||
resetToken={animToken}
|
||||
onMealPress={(mealType: 'breakfast' | 'lunch' | 'dinner' | 'snack') => {
|
||||
console.log('选择餐次:', mealType);
|
||||
// 这里可以导航到营养记录页面
|
||||
pushIfAuthedElseLogin('/nutrition/records');
|
||||
}}
|
||||
/>
|
||||
|
||||
<WeightHistoryCard />
|
||||
|
||||
{/* 真正瀑布流布局 */}
|
||||
<View style={styles.masonryContainer}>
|
||||
{/* 左列 */}
|
||||
<View style={styles.masonryColumn}>
|
||||
{/* 心情卡片 */}
|
||||
<FloatingCard style={styles.masonryCard} delay={1500}>
|
||||
<MoodCard
|
||||
moodCheckin={currentMoodCheckin}
|
||||
onPress={() => pushIfAuthedElseLogin('/mood/calendar')}
|
||||
isLoading={isMoodLoading}
|
||||
/>
|
||||
</FloatingCard>
|
||||
|
||||
{/* 右边文字区域 */}
|
||||
<View style={styles.headerTextContainer}>
|
||||
<Text style={styles.headerTitle}>海豹健康</Text>
|
||||
<FloatingCard style={styles.masonryCard}>
|
||||
<StepsCard
|
||||
stepCount={stepCount}
|
||||
stepGoal={stepGoal}
|
||||
hourlySteps={hourlySteps}
|
||||
style={styles.stepsCardOverride}
|
||||
onPress={() => pushIfAuthedElseLogin('/steps/detail')}
|
||||
/>
|
||||
</FloatingCard>
|
||||
|
||||
{/* 饮水记录卡片 */}
|
||||
<FloatingCard style={styles.masonryCard} delay={500}>
|
||||
<WaterIntakeCard
|
||||
selectedDate={currentSelectedDateString}
|
||||
style={styles.waterCardOverride}
|
||||
/>
|
||||
</FloatingCard>
|
||||
|
||||
<FloatingCard style={styles.masonryCard} delay={0}>
|
||||
<StressMeter
|
||||
value={hrvValue}
|
||||
updateTime={hrvUpdateTime}
|
||||
hrvValue={hrvValue}
|
||||
/>
|
||||
</FloatingCard>
|
||||
|
||||
{/* 心率卡片 */}
|
||||
<FloatingCard style={styles.masonryCard} delay={2000}>
|
||||
<HeartRateCard
|
||||
resetToken={animToken}
|
||||
style={styles.basalMetabolismCardOverride}
|
||||
heartRate={heartRate}
|
||||
/>
|
||||
</FloatingCard>
|
||||
</View>
|
||||
|
||||
{/* 右列 */}
|
||||
<View style={styles.masonryColumn}>
|
||||
<FloatingCard style={styles.masonryCard} delay={250}>
|
||||
<FitnessRingsCard
|
||||
activeCalories={fitnessRingsData.activeCalories}
|
||||
activeCaloriesGoal={fitnessRingsData.activeCaloriesGoal}
|
||||
exerciseMinutes={fitnessRingsData.exerciseMinutes}
|
||||
exerciseMinutesGoal={fitnessRingsData.exerciseMinutesGoal}
|
||||
standHours={fitnessRingsData.standHours}
|
||||
standHoursGoal={fitnessRingsData.standHoursGoal}
|
||||
resetToken={animToken}
|
||||
/>
|
||||
</FloatingCard>
|
||||
|
||||
<FloatingCard style={styles.masonryCard}>
|
||||
<View style={styles.cardHeaderRow}>
|
||||
<Text style={styles.cardTitle}>睡眠</Text>
|
||||
</View>
|
||||
</View>
|
||||
{sleepDuration != null ? (
|
||||
<Text style={styles.sleepValue}>
|
||||
{Math.floor(sleepDuration / 60)}小时{Math.floor(sleepDuration % 60)}分钟
|
||||
</Text>
|
||||
) : (
|
||||
<Text style={styles.sleepValue}>——</Text>
|
||||
)}
|
||||
</FloatingCard>
|
||||
|
||||
{/* 基础代谢卡片 */}
|
||||
<FloatingCard style={styles.masonryCard} delay={1250}>
|
||||
<BasalMetabolismCard
|
||||
value={basalMetabolism}
|
||||
resetToken={animToken}
|
||||
style={styles.basalMetabolismCardOverride}
|
||||
/>
|
||||
</FloatingCard>
|
||||
|
||||
{/* 血氧饱和度卡片 */}
|
||||
<FloatingCard style={styles.masonryCard} delay={1750}>
|
||||
<OxygenSaturationCard
|
||||
resetToken={animToken}
|
||||
style={styles.basalMetabolismCardOverride}
|
||||
oxygenSaturation={oxygenSaturation}
|
||||
/>
|
||||
</FloatingCard>
|
||||
|
||||
|
||||
</View>
|
||||
|
||||
|
||||
|
||||
{/* 日期选择器 */}
|
||||
<DateSelector
|
||||
selectedIndex={selectedIndex}
|
||||
onDateSelect={onSelectDate}
|
||||
showMonthTitle={false}
|
||||
disableFutureDates={true}
|
||||
/>
|
||||
|
||||
|
||||
|
||||
{/* 营养摄入雷达图卡片 */}
|
||||
<NutritionRadarCard
|
||||
nutritionSummary={nutritionSummary}
|
||||
nutritionGoals={nutritionGoals}
|
||||
burnedCalories={(basalMetabolism || 0) + (activeCalories || 0)}
|
||||
basalMetabolism={basalMetabolism || 0}
|
||||
activeCalories={activeCalories || 0}
|
||||
resetToken={animToken}
|
||||
onMealPress={(mealType: 'breakfast' | 'lunch' | 'dinner' | 'snack') => {
|
||||
console.log('选择餐次:', mealType);
|
||||
// 这里可以导航到营养记录页面
|
||||
pushIfAuthedElseLogin('/nutrition/records');
|
||||
}}
|
||||
/>
|
||||
|
||||
<WeightHistoryCard />
|
||||
|
||||
{/* 真正瀑布流布局 */}
|
||||
<View style={styles.masonryContainer}>
|
||||
{/* 左列 */}
|
||||
<View style={styles.masonryColumn}>
|
||||
{/* 心情卡片 */}
|
||||
<FloatingCard style={styles.masonryCard} delay={1500}>
|
||||
<MoodCard
|
||||
moodCheckin={currentMoodCheckin}
|
||||
onPress={() => pushIfAuthedElseLogin('/mood/calendar')}
|
||||
isLoading={isMoodLoading}
|
||||
/>
|
||||
</FloatingCard>
|
||||
|
||||
<FloatingCard style={styles.masonryCard}>
|
||||
<StepsCard
|
||||
stepCount={stepCount}
|
||||
stepGoal={stepGoal}
|
||||
hourlySteps={hourlySteps}
|
||||
style={styles.stepsCardOverride}
|
||||
onPress={() => pushIfAuthedElseLogin('/steps/detail')}
|
||||
/>
|
||||
</FloatingCard>
|
||||
|
||||
{/* 饮水记录卡片 */}
|
||||
<FloatingCard style={styles.masonryCard} delay={500}>
|
||||
<WaterIntakeCard
|
||||
selectedDate={currentSelectedDateString}
|
||||
style={styles.waterCardOverride}
|
||||
/>
|
||||
</FloatingCard>
|
||||
|
||||
<FloatingCard style={styles.masonryCard} delay={0}>
|
||||
<StressMeter
|
||||
value={hrvValue}
|
||||
updateTime={hrvUpdateTime}
|
||||
hrvValue={hrvValue}
|
||||
/>
|
||||
</FloatingCard>
|
||||
|
||||
{/* 心率卡片 */}
|
||||
<FloatingCard style={styles.masonryCard} delay={2000}>
|
||||
<HeartRateCard
|
||||
resetToken={animToken}
|
||||
style={styles.basalMetabolismCardOverride}
|
||||
heartRate={heartRate}
|
||||
/>
|
||||
</FloatingCard>
|
||||
</View>
|
||||
|
||||
{/* 右列 */}
|
||||
<View style={styles.masonryColumn}>
|
||||
<FloatingCard style={styles.masonryCard} delay={250}>
|
||||
<FitnessRingsCard
|
||||
activeCalories={fitnessRingsData.activeCalories}
|
||||
activeCaloriesGoal={fitnessRingsData.activeCaloriesGoal}
|
||||
exerciseMinutes={fitnessRingsData.exerciseMinutes}
|
||||
exerciseMinutesGoal={fitnessRingsData.exerciseMinutesGoal}
|
||||
standHours={fitnessRingsData.standHours}
|
||||
standHoursGoal={fitnessRingsData.standHoursGoal}
|
||||
resetToken={animToken}
|
||||
/>
|
||||
</FloatingCard>
|
||||
|
||||
<FloatingCard style={styles.masonryCard}>
|
||||
<View style={styles.cardHeaderRow}>
|
||||
<Text style={styles.cardTitle}>睡眠</Text>
|
||||
</View>
|
||||
{sleepDuration != null ? (
|
||||
<Text style={styles.sleepValue}>
|
||||
{Math.floor(sleepDuration / 60)}小时{Math.floor(sleepDuration % 60)}分钟
|
||||
</Text>
|
||||
) : (
|
||||
<Text style={styles.sleepValue}>——</Text>
|
||||
)}
|
||||
</FloatingCard>
|
||||
|
||||
{/* 基础代谢卡片 */}
|
||||
<FloatingCard style={styles.masonryCard} delay={1250}>
|
||||
<BasalMetabolismCard
|
||||
value={basalMetabolism}
|
||||
resetToken={animToken}
|
||||
style={styles.basalMetabolismCardOverride}
|
||||
/>
|
||||
</FloatingCard>
|
||||
|
||||
{/* 血氧饱和度卡片 */}
|
||||
<FloatingCard style={styles.masonryCard} delay={1750}>
|
||||
<OxygenSaturationCard
|
||||
resetToken={animToken}
|
||||
style={styles.basalMetabolismCardOverride}
|
||||
oxygenSaturation={oxygenSaturation}
|
||||
/>
|
||||
</FloatingCard>
|
||||
|
||||
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -771,12 +772,9 @@ const styles = StyleSheet.create({
|
||||
backgroundColor: '#0EA5E9',
|
||||
opacity: 0.05,
|
||||
},
|
||||
safeArea: {
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
scrollView: {
|
||||
flex: 1,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
headerContainer: {
|
||||
marginBottom: 10,
|
||||
|
||||
Reference in New Issue
Block a user