feat: 移除目标管理演示页面并优化相关组件
- 删除目标管理演示页面的代码,简化项目结构 - 更新底部导航,移除目标管理演示页面的路由 - 调整相关组件的样式和逻辑,确保界面一致性 - 优化颜色常量的使用,提升视觉效果
This commit is contained in:
@@ -57,7 +57,7 @@ export default function TabLayout() {
|
||||
};
|
||||
|
||||
const { icon, title } = getIconAndTitle();
|
||||
const activeContentColor = colorTokens.onPrimary;
|
||||
const activeContentColor = colorTokens.tabIconSelected; // 使用专门为Tab定义的选中颜色
|
||||
const inactiveContentColor = colorTokens.tabIconDefault;
|
||||
|
||||
return (
|
||||
|
||||
@@ -27,6 +27,7 @@ import { Colors } from '@/constants/Colors';
|
||||
import { getTabBarBottomPadding } from '@/constants/TabBar';
|
||||
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||||
import { useAuthGuard } from '@/hooks/useAuthGuard';
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import { useCosUpload } from '@/hooks/useCosUpload';
|
||||
import { deleteConversation, getConversationDetail, listConversations, type AiConversationListItem } from '@/services/aiCoach';
|
||||
import { loadAiCoachSessionCache, saveAiCoachSessionCache } from '@/services/aiCoachSession';
|
||||
@@ -124,7 +125,8 @@ export default function CoachScreen() {
|
||||
|
||||
const { isLoggedIn, pushIfAuthedElseLogin } = useAuthGuard();
|
||||
// 为了让页面更贴近品牌主题与更亮的观感,这里使用亮色系配色
|
||||
const theme = Colors.light;
|
||||
const colorScheme = useColorScheme();
|
||||
const theme = Colors[colorScheme ?? 'light'];
|
||||
const botName = (params?.name || 'Seal').toString();
|
||||
const [input, setInput] = useState('');
|
||||
const [isSending, setIsSending] = useState(false);
|
||||
@@ -282,6 +284,16 @@ export default function CoachScreen() {
|
||||
{ key: 'weight', label: '#记体重', action: () => insertWeightInputCard() },
|
||||
{ key: 'diet', label: '#记饮食', action: () => insertDietInputCard() },
|
||||
{ key: 'dietPlan', label: '#饮食方案', action: () => insertDietPlanCard() },
|
||||
{
|
||||
key: 'mood',
|
||||
label: '#记心情',
|
||||
action: () => {
|
||||
if (Platform.OS === 'ios') {
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
||||
}
|
||||
router.push('/mood/calendar');
|
||||
}
|
||||
},
|
||||
], [router, planDraft, checkin]);
|
||||
|
||||
const scrollToEnd = useCallback(() => {
|
||||
@@ -1333,7 +1345,7 @@ export default function CoachScreen() {
|
||||
{/* 标题部分 */}
|
||||
<View style={styles.dietPlanHeader}>
|
||||
<View style={styles.dietPlanTitleContainer}>
|
||||
<Ionicons name="restaurant-outline" size={20} color={Colors.light.accentGreenDark} />
|
||||
<Ionicons name="restaurant-outline" size={20} color={theme.success} />
|
||||
<Text style={styles.dietPlanTitle}>我的饮食方案</Text>
|
||||
</View>
|
||||
<Text style={styles.dietPlanSubtitle}>MY DIET PLAN</Text>
|
||||
@@ -1522,7 +1534,7 @@ export default function CoachScreen() {
|
||||
</View>
|
||||
)}
|
||||
{isSelected && isPending && (
|
||||
<ActivityIndicator size="small" color={Colors.light.accentGreenDark} />
|
||||
<ActivityIndicator size="small" color={theme.success} />
|
||||
)}
|
||||
{isSelected && !isPending && (
|
||||
<View style={styles.selectedBadge}>
|
||||
@@ -1829,7 +1841,7 @@ export default function CoachScreen() {
|
||||
<View style={styles.screen}>
|
||||
{/* 背景渐变 */}
|
||||
<LinearGradient
|
||||
colors={['#F0F9FF', '#E0F2FE']}
|
||||
colors={['#fafaff', '#f4f3ff']} // 使用紫色主题的浅色渐变
|
||||
style={styles.gradientBackground}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
@@ -1866,7 +1878,7 @@ export default function CoachScreen() {
|
||||
source={require('@/assets/images/icons/iconFlash.png')}
|
||||
style={styles.usageIcon}
|
||||
/>
|
||||
<Text style={[styles.usageText, { color: theme.text }]}>
|
||||
<Text style={styles.usageText}>
|
||||
{userProfile?.isVip ? '不限' : `${userProfile?.freeUsageCount || 0}/${userProfile?.maxUsageCount || 0}`}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
@@ -1876,16 +1888,16 @@ export default function CoachScreen() {
|
||||
<TouchableOpacity
|
||||
accessibilityRole="button"
|
||||
onPress={startNewConversation}
|
||||
style={[styles.headerActionButton, { backgroundColor: `${Colors.light.accentGreen}33` }]} // 20% opacity
|
||||
style={[styles.headerActionButton, { backgroundColor: `${theme.primary}20` }]} // 20% opacity
|
||||
>
|
||||
<Ionicons name="add-outline" size={18} color={theme.onPrimary} />
|
||||
<Ionicons name="add-outline" size={18} color={theme.primary} />
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
accessibilityRole="button"
|
||||
onPress={openHistory}
|
||||
style={[styles.headerActionButton, { backgroundColor: `${Colors.light.accentGreen}33` }]} // 20% opacity
|
||||
style={[styles.headerActionButton, { backgroundColor: `${theme.primary}20` }]} // 20% opacity
|
||||
>
|
||||
<Ionicons name="time-outline" size={18} color={theme.onPrimary} />
|
||||
<Ionicons name="time-outline" size={18} color={theme.primary} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
@@ -1948,8 +1960,18 @@ export default function CoachScreen() {
|
||||
contentContainerStyle={{ paddingHorizontal: 6, gap: 8 }}
|
||||
>
|
||||
{chips.map((c) => (
|
||||
<TouchableOpacity key={c.key} style={[styles.chip, { borderColor: `${Colors.light.accentGreen}59`, backgroundColor: `${Colors.light.accentGreen}1F` }]} onPress={c.action}>
|
||||
<Text style={[styles.chipText, { color: '#192126' }]}>{c.label}</Text>
|
||||
<TouchableOpacity
|
||||
key={c.key}
|
||||
style={[
|
||||
styles.chip,
|
||||
{
|
||||
borderColor: c.key === 'mood' ? `${theme.success}40` : `${theme.primary}40`,
|
||||
backgroundColor: c.key === 'mood' ? `${theme.success}15` : `${theme.primary}15`
|
||||
}
|
||||
]}
|
||||
onPress={c.action}
|
||||
>
|
||||
<Text style={[styles.chipText, { color: c.key === 'mood' ? theme.success : theme.text }]}>{c.label}</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</ScrollView>
|
||||
@@ -1990,18 +2012,18 @@ export default function CoachScreen() {
|
||||
</ScrollView>
|
||||
)}
|
||||
|
||||
<View style={[styles.inputRow, { borderColor: `${Colors.light.accentGreen}59`, backgroundColor: `${Colors.light.accentGreen}14` }]}>
|
||||
<View style={[styles.inputRow, { borderColor: `${theme.primary}30`, backgroundColor: `${theme.primary}08` }]}>
|
||||
<TouchableOpacity
|
||||
accessibilityRole="button"
|
||||
onPress={pickImages}
|
||||
style={[styles.mediaBtn, { backgroundColor: `${Colors.light.accentGreen}28` }]}
|
||||
style={[styles.mediaBtn, { backgroundColor: `${theme.primary}20` }]}
|
||||
>
|
||||
<Ionicons name="image-outline" size={18} color={'#192126'} />
|
||||
<Ionicons name="image-outline" size={18} color={theme.text} />
|
||||
</TouchableOpacity>
|
||||
<TextInput
|
||||
placeholder="问我任何健康相关的问题,如营养、健身、生活管理等..."
|
||||
placeholderTextColor={theme.textMuted}
|
||||
style={[styles.input, { color: '#192126' }]}
|
||||
style={[styles.input, { color: theme.text }]}
|
||||
value={input}
|
||||
onChangeText={setInput}
|
||||
multiline
|
||||
@@ -2021,7 +2043,7 @@ export default function CoachScreen() {
|
||||
style={[
|
||||
styles.sendBtn,
|
||||
{
|
||||
backgroundColor: (isSending || isStreaming) ? '#FF4444' : theme.primary,
|
||||
backgroundColor: (isSending || isStreaming) ? theme.danger : theme.primary,
|
||||
opacity: ((input.trim() || selectedImages.length > 0) || (isSending || isStreaming)) ? 1 : 0.5
|
||||
}
|
||||
]}
|
||||
@@ -2144,8 +2166,8 @@ const styles = StyleSheet.create({
|
||||
width: 60,
|
||||
height: 60,
|
||||
borderRadius: 30,
|
||||
backgroundColor: '#0EA5E9',
|
||||
opacity: 0.1,
|
||||
backgroundColor: '#7a5af8', // 紫色主题
|
||||
opacity: 0.08,
|
||||
},
|
||||
decorativeCircle2: {
|
||||
position: 'absolute',
|
||||
@@ -2154,8 +2176,8 @@ const styles = StyleSheet.create({
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 20,
|
||||
backgroundColor: '#0EA5E9',
|
||||
opacity: 0.05,
|
||||
backgroundColor: '#7a5af8', // 紫色主题
|
||||
opacity: 0.04,
|
||||
},
|
||||
headerLeft: {
|
||||
flexDirection: 'row',
|
||||
@@ -2177,6 +2199,11 @@ const styles = StyleSheet.create({
|
||||
borderRadius: 16,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
shadowColor: '#7a5af8',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
elevation: 2,
|
||||
},
|
||||
historyButton: {
|
||||
width: 32,
|
||||
@@ -2237,7 +2264,7 @@ const styles = StyleSheet.create({
|
||||
borderRadius: 8,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: `${Colors.light.accentGreen}99` // 60% opacity
|
||||
backgroundColor: '#7a5af899' // 紫色主题 60% opacity
|
||||
},
|
||||
dietOptionsContainer: {
|
||||
gap: 8,
|
||||
@@ -2249,13 +2276,13 @@ const styles = StyleSheet.create({
|
||||
borderRadius: 12,
|
||||
backgroundColor: 'rgba(255,255,255,0.9)',
|
||||
borderWidth: 1,
|
||||
borderColor: `${Colors.light.accentGreen}4D`, // 30% opacity
|
||||
borderColor: '#7a5af84d', // 紫色主题 30% opacity
|
||||
},
|
||||
dietOptionIconContainer: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 20,
|
||||
backgroundColor: `${Colors.light.accentGreen}33`, // 20% opacity
|
||||
backgroundColor: '#7a5af833', // 紫色主题 20% opacity
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginRight: 12,
|
||||
@@ -2304,7 +2331,7 @@ const styles = StyleSheet.create({
|
||||
borderRadius: 10,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: `${Colors.light.accentGreen}99`, // 60% opacity
|
||||
backgroundColor: '#7a5af899', // 紫色主题 60% opacity
|
||||
alignSelf: 'flex-end',
|
||||
},
|
||||
// markdown 基础样式承载容器的字体尺寸保持与气泡一致
|
||||
@@ -2349,7 +2376,7 @@ const styles = StyleSheet.create({
|
||||
borderRadius: 12,
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
backgroundColor: 'rgba(0,0,0,0.06)'
|
||||
backgroundColor: 'rgba(122,90,248,0.08)' // 使用紫色主题的浅色背景
|
||||
},
|
||||
imageThumb: {
|
||||
width: '100%',
|
||||
@@ -2404,7 +2431,7 @@ const styles = StyleSheet.create({
|
||||
padding: 8,
|
||||
borderWidth: 1,
|
||||
borderRadius: 16,
|
||||
backgroundColor: 'rgba(0,0,0,0.04)'
|
||||
backgroundColor: 'rgba(122,90,248,0.04)' // 使用紫色主题的极浅色背景
|
||||
},
|
||||
mediaBtn: {
|
||||
width: 40,
|
||||
@@ -2617,17 +2644,17 @@ const styles = StyleSheet.create({
|
||||
choiceButton: {
|
||||
backgroundColor: 'rgba(255,255,255,0.9)',
|
||||
borderWidth: 1,
|
||||
borderColor: `${Colors.light.accentGreen}4D`, // 30% opacity
|
||||
borderColor: '#7a5af84d', // 紫色主题 30% opacity
|
||||
borderRadius: 12,
|
||||
padding: 12,
|
||||
},
|
||||
choiceButtonRecommended: {
|
||||
borderColor: `${Colors.light.accentGreen}99`, // 60% opacity
|
||||
backgroundColor: `${Colors.light.accentGreen}1A`, // 10% opacity
|
||||
borderColor: '#7a5af899', // 紫色主题 60% opacity
|
||||
backgroundColor: '#7a5af81a', // 紫色主题 10% opacity
|
||||
},
|
||||
choiceButtonSelected: {
|
||||
borderColor: Colors.light.accentGreenDark,
|
||||
backgroundColor: `${Colors.light.accentGreen}33`, // 20% opacity
|
||||
borderColor: '#19b36e', // success[500]
|
||||
backgroundColor: '#19b36e33', // 20% opacity
|
||||
borderWidth: 2,
|
||||
},
|
||||
choiceButtonDisabled: {
|
||||
@@ -2647,10 +2674,10 @@ const styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
},
|
||||
choiceLabelRecommended: {
|
||||
color: Colors.light.accentGreenDark,
|
||||
color: '#19b36e', // success[500]
|
||||
},
|
||||
choiceLabelSelected: {
|
||||
color: Colors.light.accentGreenDark,
|
||||
color: '#19b36e', // success[500]
|
||||
fontWeight: '700',
|
||||
},
|
||||
choiceLabelDisabled: {
|
||||
@@ -2662,7 +2689,7 @@ const styles = StyleSheet.create({
|
||||
gap: 8,
|
||||
},
|
||||
recommendedBadge: {
|
||||
backgroundColor: `${Colors.light.accentGreen}CC`, // 80% opacity
|
||||
backgroundColor: '#7a5af8cc', // 紫色主题 80% opacity
|
||||
borderRadius: 6,
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 2,
|
||||
@@ -2670,10 +2697,10 @@ const styles = StyleSheet.create({
|
||||
recommendedText: {
|
||||
fontSize: 12,
|
||||
fontWeight: '700',
|
||||
color: Colors.light.accentGreenDark,
|
||||
color: '#19b36e', // success[500]
|
||||
},
|
||||
selectedBadge: {
|
||||
backgroundColor: Colors.light.accentGreenDark,
|
||||
backgroundColor: '#19b36e', // success[500]
|
||||
borderRadius: 6,
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 2,
|
||||
@@ -2714,7 +2741,7 @@ const styles = StyleSheet.create({
|
||||
padding: 16,
|
||||
gap: 16,
|
||||
borderWidth: 1,
|
||||
borderColor: `${Colors.light.accentGreen}33`, // 20% opacity
|
||||
borderColor: '#7a5af833', // 紫色主题 20% opacity
|
||||
},
|
||||
dietPlanHeader: {
|
||||
gap: 4,
|
||||
@@ -2834,7 +2861,7 @@ const styles = StyleSheet.create({
|
||||
caloriesValue: {
|
||||
fontSize: 18,
|
||||
fontWeight: '800',
|
||||
color: Colors.light.accentGreenDark,
|
||||
color: '#19b36e', // success[500]
|
||||
},
|
||||
nutritionGrid: {
|
||||
flexDirection: 'row',
|
||||
@@ -2871,7 +2898,7 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: 8,
|
||||
backgroundColor: Colors.light.accentGreenDark,
|
||||
backgroundColor: '#19b36e', // success[500]
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 16,
|
||||
borderRadius: 12,
|
||||
@@ -2889,7 +2916,7 @@ const styles = StyleSheet.create({
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 12,
|
||||
backgroundColor: 'rgba(0,0,0,0.06)',
|
||||
backgroundColor: 'rgba(122,90,248,0.08)', // 紫色主题浅色背景
|
||||
},
|
||||
usageIcon: {
|
||||
width: 16,
|
||||
@@ -2898,7 +2925,7 @@ const styles = StyleSheet.create({
|
||||
usageText: {
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
color: '#687076',
|
||||
color: '#7a5af8', // 紫色主题文字颜色
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import { DEFAULT_MEMBER_NAME, fetchActivityHistory, fetchMyProfile } from '@/sto
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { Image, Linking, SafeAreaView, ScrollView, StatusBar, StyleSheet, Switch, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
@@ -31,7 +30,6 @@ export default function PersonalScreen() {
|
||||
|
||||
// 颜色主题
|
||||
const colors = Colors[colorScheme ?? 'light'];
|
||||
const theme = (colorScheme ?? 'light') as 'light' | 'dark';
|
||||
|
||||
// 直接使用 Redux 中的用户信息,避免重复状态管理
|
||||
const userProfile = useAppSelector((state) => state.user.profile);
|
||||
@@ -66,129 +64,100 @@ export default function PersonalScreen() {
|
||||
// 显示名称
|
||||
const displayName = (userProfile.name?.trim()) ? userProfile.name : DEFAULT_MEMBER_NAME;
|
||||
|
||||
// 颜色令牌
|
||||
const colorTokens = colors;
|
||||
|
||||
const UserInfoSection = () => (
|
||||
<View style={[styles.userInfoCard, { backgroundColor: colorTokens.card }]}>
|
||||
<View style={styles.userInfoContainer}>
|
||||
{/* 头像 */}
|
||||
<View style={styles.avatarContainer}>
|
||||
<View style={[styles.avatar, { backgroundColor: colorTokens.ornamentAccent }]}>
|
||||
<Image source={{ uri: userProfile.avatar || DEFAULT_AVATAR_URL }} style={{ width: '100%', height: '100%' }} />
|
||||
// 用户信息头部
|
||||
const UserHeader = () => (
|
||||
<View style={styles.sectionContainer}>
|
||||
<Text style={styles.sectionTitle}>个人信息</Text>
|
||||
<View style={styles.cardContainer}>
|
||||
<View style={styles.userInfoContainer}>
|
||||
<View style={styles.avatarContainer}>
|
||||
<Image
|
||||
source={{ uri: userProfile.avatar || DEFAULT_AVATAR_URL }}
|
||||
style={styles.avatar}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.userDetails}>
|
||||
<Text style={styles.userName}>{displayName}</Text>
|
||||
</View>
|
||||
<TouchableOpacity style={styles.editButton} onPress={() => pushIfAuthedElseLogin('/profile/edit')}>
|
||||
<Text style={styles.editButtonText}>编辑</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* 用户信息 */}
|
||||
<View style={styles.userDetails}>
|
||||
<Text style={[styles.userName, { color: colorTokens.text }]}>{displayName}</Text>
|
||||
</View>
|
||||
|
||||
{/* 编辑按钮 */}
|
||||
<TouchableOpacity style={dynamicStyles.editButton} onPress={() => pushIfAuthedElseLogin('/profile/edit')}>
|
||||
<Text style={dynamicStyles.editButtonText}>编辑</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
// 数据统计部分
|
||||
const StatsSection = () => (
|
||||
<View style={[styles.statsContainer, { backgroundColor: colorTokens.card }]}>
|
||||
<View style={styles.statItem}>
|
||||
<Text style={dynamicStyles.statValue}>{formatHeight()}</Text>
|
||||
<Text style={[styles.statLabel, { color: colorTokens.textMuted }]}>身高</Text>
|
||||
</View>
|
||||
<View style={styles.statItem}>
|
||||
<Text style={dynamicStyles.statValue}>{formatWeight()}</Text>
|
||||
<Text style={[styles.statLabel, { color: colorTokens.textMuted }]}>体重</Text>
|
||||
</View>
|
||||
<View style={styles.statItem}>
|
||||
<Text style={dynamicStyles.statValue}>{formatAge()}</Text>
|
||||
<Text style={[styles.statLabel, { color: colorTokens.textMuted }]}>年龄</Text>
|
||||
<View style={styles.sectionContainer}>
|
||||
<Text style={styles.sectionTitle}>身体数据</Text>
|
||||
<View style={styles.cardContainer}>
|
||||
<View style={styles.statsContainer}>
|
||||
<View style={styles.statItem}>
|
||||
<Text style={styles.statValue}>{formatHeight()}</Text>
|
||||
<Text style={styles.statLabel}>身高</Text>
|
||||
</View>
|
||||
<View style={styles.statItem}>
|
||||
<Text style={styles.statValue}>{formatWeight()}</Text>
|
||||
<Text style={styles.statLabel}>体重</Text>
|
||||
</View>
|
||||
<View style={styles.statItem}>
|
||||
<Text style={styles.statValue}>{formatAge()}</Text>
|
||||
<Text style={styles.statLabel}>年龄</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
// 菜单项组件
|
||||
const MenuSection = ({ title, items }: { title: string; items: any[] }) => (
|
||||
<View style={[styles.menuSection, { backgroundColor: colorTokens.card }]}>
|
||||
<Text style={[styles.sectionTitle, { color: colorTokens.text }]}>{title}</Text>
|
||||
{items.map((item, index) => (
|
||||
<TouchableOpacity
|
||||
key={index}
|
||||
style={styles.menuItem}
|
||||
onPress={item.type === 'switch' ? undefined : item.onPress}
|
||||
disabled={item.type === 'switch'}
|
||||
>
|
||||
<View style={styles.menuItemLeft}>
|
||||
<View style={[
|
||||
styles.menuIcon,
|
||||
{ backgroundColor: item.isDanger ? 'rgba(255,68,68,0.12)' : 'rgba(135,206,235,0.15)' }
|
||||
]}>
|
||||
<Ionicons
|
||||
name={item.icon}
|
||||
size={20}
|
||||
color={item.isDanger ? colors.danger : '#4682B4'}
|
||||
/>
|
||||
<View style={styles.sectionContainer}>
|
||||
<Text style={styles.sectionTitle}>{title}</Text>
|
||||
<View style={styles.cardContainer}>
|
||||
{items.map((item, index) => (
|
||||
<TouchableOpacity
|
||||
key={index}
|
||||
style={[styles.menuItem, index === items.length - 1 && { borderBottomWidth: 0 }]}
|
||||
onPress={item.type === 'switch' ? undefined : item.onPress}
|
||||
disabled={item.type === 'switch'}
|
||||
>
|
||||
<View style={styles.menuItemLeft}>
|
||||
<View style={[
|
||||
styles.iconContainer,
|
||||
{ backgroundColor: item.isDanger ? 'rgba(255,68,68,0.1)' : 'rgba(147, 112, 219, 0.1)' }
|
||||
]}>
|
||||
<Ionicons
|
||||
name={item.icon}
|
||||
size={20}
|
||||
color={item.isDanger ? '#FF4444' : '#9370DB'}
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.menuItemText}>{item.title}</Text>
|
||||
</View>
|
||||
<Text style={[styles.menuItemText, { color: colorTokens.text }]}>{item.title}</Text>
|
||||
</View>
|
||||
{item.type === 'switch' ? (
|
||||
<Switch
|
||||
value={isLoggedIn ? notificationEnabled : false}
|
||||
onValueChange={(value) => {
|
||||
if (!isLoggedIn) {
|
||||
pushIfAuthedElseLogin('/profile/notification-settings');
|
||||
return;
|
||||
}
|
||||
setNotificationEnabled(value);
|
||||
}}
|
||||
trackColor={{ false: '#E5E5E5', true: colors.primary }}
|
||||
thumbColor="#FFFFFF"
|
||||
style={styles.switch}
|
||||
/>
|
||||
) : (
|
||||
<Ionicons name="chevron-forward" size={20} color={colorTokens.icon} />
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
{item.type === 'switch' ? (
|
||||
<Switch
|
||||
value={isLoggedIn ? notificationEnabled : false}
|
||||
onValueChange={(value) => {
|
||||
if (!isLoggedIn) {
|
||||
pushIfAuthedElseLogin('/profile/notification-settings');
|
||||
return;
|
||||
}
|
||||
setNotificationEnabled(value);
|
||||
}}
|
||||
trackColor={{ false: '#E5E5E5', true: '#9370DB' }}
|
||||
thumbColor="#FFFFFF"
|
||||
style={styles.switch}
|
||||
/>
|
||||
) : (
|
||||
<Ionicons name="chevron-forward" size={20} color="#CCCCCC" />
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
// 动态样式
|
||||
const dynamicStyles = StyleSheet.create({
|
||||
editButton: {
|
||||
backgroundColor: colors.primary,
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 10,
|
||||
borderRadius: 20,
|
||||
},
|
||||
editButtonText: {
|
||||
color: colors.onPrimary,
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
},
|
||||
statValue: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
color: colors.primary,
|
||||
marginBottom: 4,
|
||||
},
|
||||
floatingButton: {
|
||||
width: 56,
|
||||
height: 56,
|
||||
borderRadius: 28,
|
||||
backgroundColor: colors.primary,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
shadowColor: colors.primary,
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 8,
|
||||
elevation: 8,
|
||||
},
|
||||
});
|
||||
|
||||
// 菜单项配置
|
||||
const menuSections = [
|
||||
{
|
||||
@@ -197,25 +166,8 @@ export default function PersonalScreen() {
|
||||
{
|
||||
icon: 'flag-outline' as const,
|
||||
title: '目标管理',
|
||||
onPress: () => pushIfAuthedElseLogin('/goals'),
|
||||
onPress: () => pushIfAuthedElseLogin('/profile/goals'),
|
||||
},
|
||||
{
|
||||
icon: 'telescope-outline' as const,
|
||||
title: '目标管理演示',
|
||||
onPress: () => pushIfAuthedElseLogin('/goal-demo'),
|
||||
},
|
||||
// {
|
||||
// icon: 'stats-chart-outline' as const,
|
||||
// title: '训练进度',
|
||||
// onPress: () => {
|
||||
// // 训练进度页面暂未实现,先显示提示
|
||||
// if (isLoggedIn) {
|
||||
// Alert.alert('提示', '训练进度功能正在开发中');
|
||||
// } else {
|
||||
// pushIfAuthedElseLogin('/profile/training-progress');
|
||||
// }
|
||||
// },
|
||||
// },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -265,12 +217,6 @@ export default function PersonalScreen() {
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<LinearGradient
|
||||
colors={[colors.backgroundGradientStart, colors.backgroundGradientEnd]}
|
||||
style={styles.gradientBackground}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 0, y: 1 }}
|
||||
/>
|
||||
<StatusBar barStyle={'dark-content'} backgroundColor="transparent" translucent />
|
||||
<SafeAreaView style={styles.safeArea}>
|
||||
<ScrollView
|
||||
@@ -278,13 +224,12 @@ export default function PersonalScreen() {
|
||||
contentContainerStyle={{ paddingBottom: bottomPadding }}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<UserInfoSection />
|
||||
<UserHeader />
|
||||
<StatsSection />
|
||||
<ActivityHeatMap />
|
||||
{menuSections.map((section, index) => (
|
||||
<MenuSection key={index} title={section.title} items={section.items} />
|
||||
))}
|
||||
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
@@ -294,48 +239,53 @@ export default function PersonalScreen() {
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
gradientBackground: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: '#FAFAFA',
|
||||
},
|
||||
safeArea: {
|
||||
flex: 1,
|
||||
},
|
||||
scrollView: {
|
||||
flex: 1,
|
||||
paddingHorizontal: 20,
|
||||
paddingHorizontal: 16,
|
||||
paddingTop: 16,
|
||||
},
|
||||
// 部分容器
|
||||
sectionContainer: {
|
||||
marginBottom: 20,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
color: '#2C3E50',
|
||||
marginBottom: 10,
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
// 卡片容器
|
||||
cardContainer: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderRadius: 12,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 1 },
|
||||
shadowOpacity: 0.05,
|
||||
shadowRadius: 4,
|
||||
elevation: 2,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
// 用户信息区域
|
||||
userInfoCard: {
|
||||
borderRadius: 16,
|
||||
marginBottom: 20,
|
||||
shadowColor: 'rgba(135,206,235,0.3)',
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.15,
|
||||
shadowRadius: 8,
|
||||
elevation: 3,
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(135,206,235,0.1)',
|
||||
},
|
||||
userInfoContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
padding: 20,
|
||||
padding: 16,
|
||||
},
|
||||
avatarContainer: {
|
||||
marginRight: 15,
|
||||
marginRight: 12,
|
||||
},
|
||||
avatar: {
|
||||
width: 80,
|
||||
height: 80,
|
||||
borderRadius: 40,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
overflow: 'hidden',
|
||||
width: 60,
|
||||
height: 60,
|
||||
borderRadius: 30,
|
||||
borderWidth: 2,
|
||||
borderColor: '#9370DB',
|
||||
},
|
||||
userDetails: {
|
||||
flex: 1,
|
||||
@@ -343,86 +293,75 @@ const styles = StyleSheet.create({
|
||||
userName: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
color: '#2C3E50',
|
||||
marginBottom: 4,
|
||||
},
|
||||
userRole: {
|
||||
fontSize: 14,
|
||||
color: '#9370DB',
|
||||
fontWeight: '500',
|
||||
},
|
||||
editButton: {
|
||||
backgroundColor: '#9370DB',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 16,
|
||||
},
|
||||
editButtonText: {
|
||||
color: '#FFFFFF',
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
},
|
||||
// 数据统计
|
||||
statsContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
borderRadius: 16,
|
||||
padding: 20,
|
||||
marginBottom: 20,
|
||||
shadowColor: 'rgba(135,206,235,0.25)',
|
||||
shadowOffset: { width: 0, height: 3 },
|
||||
shadowOpacity: 0.12,
|
||||
shadowRadius: 6,
|
||||
elevation: 3,
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(135,206,235,0.08)',
|
||||
padding: 16,
|
||||
},
|
||||
statItem: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
statValue: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
color: '#9370DB',
|
||||
marginBottom: 4,
|
||||
},
|
||||
statLabel: {
|
||||
fontSize: 12,
|
||||
color: '#6C757D',
|
||||
fontWeight: '500',
|
||||
},
|
||||
menuSection: {
|
||||
marginBottom: 20,
|
||||
padding: 16,
|
||||
borderRadius: 16,
|
||||
shadowColor: 'rgba(135,206,235,0.2)',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
elevation: 2,
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(135,206,235,0.06)',
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 20,
|
||||
fontWeight: '800',
|
||||
marginBottom: 12,
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
// 菜单项
|
||||
menuItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingVertical: 16,
|
||||
paddingVertical: 14,
|
||||
paddingHorizontal: 16,
|
||||
borderRadius: 12,
|
||||
marginBottom: 8,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#F1F3F4',
|
||||
},
|
||||
menuItemLeft: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
},
|
||||
menuIcon: {
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: 8,
|
||||
iconContainer: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 6,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginRight: 12,
|
||||
},
|
||||
menuItemText: {
|
||||
fontSize: 16,
|
||||
flex: 1,
|
||||
fontWeight: '600',
|
||||
fontSize: 15,
|
||||
color: '#2C3E50',
|
||||
fontWeight: '500',
|
||||
},
|
||||
switch: {
|
||||
transform: [{ scaleX: 0.8 }, { scaleY: 0.8 }],
|
||||
},
|
||||
// 浮动按钮
|
||||
floatingButtonContainer: {
|
||||
position: 'absolute',
|
||||
bottom: 30,
|
||||
left: 0,
|
||||
right: 0,
|
||||
alignItems: 'center',
|
||||
pointerEvents: 'box-none',
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
@@ -22,7 +22,6 @@ import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import dayjs from 'dayjs';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { router } from 'expo-router';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
Animated,
|
||||
@@ -120,6 +119,9 @@ export default function ExploreScreen() {
|
||||
standHours: 0,
|
||||
standHoursGoal: 12
|
||||
});
|
||||
// 血氧饱和度和心率数据
|
||||
const [oxygenSaturation, setOxygenSaturation] = useState<number | null>(null);
|
||||
const [heartRate, setHeartRate] = useState<number | null>(null);
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
// 用于触发动画重置的 token(当日期或数据变化时更新)
|
||||
@@ -225,6 +227,13 @@ export default function ExploreScreen() {
|
||||
// 更新HRV数据时间
|
||||
setHrvUpdateTime(new Date());
|
||||
|
||||
// 设置血氧饱和度和心率数据
|
||||
setOxygenSaturation(data.oxygenSaturation ?? null);
|
||||
setHeartRate(data.heartRate ?? null);
|
||||
|
||||
console.log('血氧饱和度数据:', data.oxygenSaturation);
|
||||
console.log('心率数据:', data.heartRate);
|
||||
|
||||
setAnimToken((t) => t + 1);
|
||||
} else {
|
||||
console.log('忽略过期健康数据请求结果,key=', requestKey, '最新key=', latestRequestKeyRef.current);
|
||||
@@ -233,6 +242,9 @@ export default function ExploreScreen() {
|
||||
|
||||
} catch (error) {
|
||||
console.error('HealthKit流程出现异常:', error);
|
||||
// 重置血氧饱和度和心率数据
|
||||
setOxygenSaturation(null);
|
||||
setHeartRate(null);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -389,7 +401,7 @@ export default function ExploreScreen() {
|
||||
<FloatingCard style={[styles.masonryCard, styles.moodCard]} delay={1500}>
|
||||
<MoodCard
|
||||
moodCheckin={currentMoodCheckin}
|
||||
onPress={() => router.push('/mood/calendar')}
|
||||
onPress={() => pushIfAuthedElseLogin('/mood/calendar')}
|
||||
isLoading={isMoodLoading}
|
||||
/>
|
||||
</FloatingCard>
|
||||
@@ -436,6 +448,7 @@ export default function ExploreScreen() {
|
||||
<OxygenSaturationCard
|
||||
resetToken={animToken}
|
||||
style={styles.basalMetabolismCardOverride}
|
||||
oxygenSaturation={oxygenSaturation}
|
||||
/>
|
||||
</FloatingCard>
|
||||
|
||||
@@ -444,6 +457,7 @@ export default function ExploreScreen() {
|
||||
<HeartRateCard
|
||||
resetToken={animToken}
|
||||
style={styles.basalMetabolismCardOverride}
|
||||
heartRate={heartRate}
|
||||
/>
|
||||
</FloatingCard>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user