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:
richarjiang
2025-09-03 19:17:26 +08:00
parent 45f8415a38
commit 02883869fe
10 changed files with 931 additions and 233 deletions

View File

@@ -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,