feat: 更新教练页面和布局,优化用户体验
- 将教练页面中的“Bot”名称更改为“Seal”,提升品牌一致性 - 在布局文件中调整标签标题和图标,确保与新名称一致 - 新增使用次数显示功能,优化用户对使用情况的了解 - 更新日期选择器样式,增强未来日期的禁用效果 - 优化压力分析模态框的颜色和文本,提升可读性
This commit is contained in:
@@ -43,7 +43,7 @@ export default function TabLayout() {
|
|||||||
case 'explore':
|
case 'explore':
|
||||||
return { icon: 'magnifyingglass.circle.fill', title: '发现' } as const;
|
return { icon: 'magnifyingglass.circle.fill', title: '发现' } as const;
|
||||||
case 'coach':
|
case 'coach':
|
||||||
return { icon: 'person.3.fill', title: 'Bot' } as const;
|
return { icon: 'person.3.fill', title: 'Seal' } as const;
|
||||||
case 'statistics':
|
case 'statistics':
|
||||||
return { icon: 'chart.pie.fill', title: '统计' } as const;
|
return { icon: 'chart.pie.fill', title: '统计' } as const;
|
||||||
case 'personal':
|
case 'personal':
|
||||||
@@ -131,7 +131,7 @@ export default function TabLayout() {
|
|||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
name="coach"
|
name="coach"
|
||||||
options={{
|
options={{
|
||||||
title: 'Bot',
|
title: 'Seal',
|
||||||
tabBarIcon: ({ color }) => {
|
tabBarIcon: ({ color }) => {
|
||||||
const isCoachSelected = pathname === '/coach';
|
const isCoachSelected = pathname === '/coach';
|
||||||
return (
|
return (
|
||||||
@@ -148,7 +148,7 @@ export default function TabLayout() {
|
|||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
}}>
|
}}>
|
||||||
Bot
|
Seal
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
@@ -160,6 +160,7 @@ export default function TabLayout() {
|
|||||||
name="explore"
|
name="explore"
|
||||||
options={{
|
options={{
|
||||||
title: '发现',
|
title: '发现',
|
||||||
|
href: null,
|
||||||
tabBarIcon: ({ color }) => {
|
tabBarIcon: ({ color }) => {
|
||||||
const isHomeSelected = pathname === '/' || pathname === '/index';
|
const isHomeSelected = pathname === '/' || pathname === '/index';
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -25,12 +25,13 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|||||||
|
|
||||||
import { Colors } from '@/constants/Colors';
|
import { Colors } from '@/constants/Colors';
|
||||||
import { getTabBarBottomPadding } from '@/constants/TabBar';
|
import { getTabBarBottomPadding } from '@/constants/TabBar';
|
||||||
import { useAppSelector } from '@/hooks/redux';
|
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||||||
import { useAuthGuard } from '@/hooks/useAuthGuard';
|
import { useAuthGuard } from '@/hooks/useAuthGuard';
|
||||||
import { useCosUpload } from '@/hooks/useCosUpload';
|
import { useCosUpload } from '@/hooks/useCosUpload';
|
||||||
import { deleteConversation, getConversationDetail, listConversations, type AiConversationListItem } from '@/services/aiCoach';
|
import { deleteConversation, getConversationDetail, listConversations, type AiConversationListItem } from '@/services/aiCoach';
|
||||||
import { loadAiCoachSessionCache, saveAiCoachSessionCache } from '@/services/aiCoachSession';
|
import { loadAiCoachSessionCache, saveAiCoachSessionCache } from '@/services/aiCoachSession';
|
||||||
import { api, getAuthToken, postTextStream } from '@/services/api';
|
import { api, getAuthToken, postTextStream } from '@/services/api';
|
||||||
|
import { updateProfile } from '@/store/userSlice';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { LinearGradient } from 'expo-linear-gradient';
|
import { LinearGradient } from 'expo-linear-gradient';
|
||||||
import { ActionSheet } from '../../components/ui/ActionSheet';
|
import { ActionSheet } from '../../components/ui/ActionSheet';
|
||||||
@@ -124,7 +125,7 @@ export default function CoachScreen() {
|
|||||||
const { isLoggedIn, pushIfAuthedElseLogin } = useAuthGuard();
|
const { isLoggedIn, pushIfAuthedElseLogin } = useAuthGuard();
|
||||||
// 为了让页面更贴近品牌主题与更亮的观感,这里使用亮色系配色
|
// 为了让页面更贴近品牌主题与更亮的观感,这里使用亮色系配色
|
||||||
const theme = Colors.light;
|
const theme = Colors.light;
|
||||||
const botName = (params?.name || 'Bot').toString();
|
const botName = (params?.name || 'Seal').toString();
|
||||||
const [input, setInput] = useState('');
|
const [input, setInput] = useState('');
|
||||||
const [isSending, setIsSending] = useState(false);
|
const [isSending, setIsSending] = useState(false);
|
||||||
const [isStreaming, setIsStreaming] = useState(false);
|
const [isStreaming, setIsStreaming] = useState(false);
|
||||||
@@ -174,7 +175,7 @@ export default function CoachScreen() {
|
|||||||
const generateWelcomeMessage = useCallback(() => {
|
const generateWelcomeMessage = useCallback(() => {
|
||||||
const hour = new Date().getHours();
|
const hour = new Date().getHours();
|
||||||
const name = userProfile?.name || '朋友';
|
const name = userProfile?.name || '朋友';
|
||||||
const botName = (params?.name || '海豹助手').toString();
|
const botName = (params?.name || 'Seal').toString();
|
||||||
|
|
||||||
// 时段问候
|
// 时段问候
|
||||||
let timeGreeting = '';
|
let timeGreeting = '';
|
||||||
@@ -199,7 +200,7 @@ export default function CoachScreen() {
|
|||||||
messages: [
|
messages: [
|
||||||
`${timeGreeting},${name}!我是${botName},你的专属健康管理助手。新的一天开始了,让我们一起为你的健康目标努力吧!`,
|
`${timeGreeting},${name}!我是${botName},你的专属健康管理助手。新的一天开始了,让我们一起为你的健康目标努力吧!`,
|
||||||
`${timeGreeting}!早晨是制定健康计划的最佳时机,我是${botName},可以帮你管理营养摄入、运动计划和生活作息。`,
|
`${timeGreeting}!早晨是制定健康计划的最佳时机,我是${botName},可以帮你管理营养摄入、运动计划和生活作息。`,
|
||||||
`${timeGreeting},${name}!作为你的海豹助手,我很高兴能陪伴你的健康之旅。无论是饮食营养、健身锻炼还是生活管理,我都能为你提供专业建议。`
|
`${timeGreeting},${name}!作为你的Seal,我很高兴能陪伴你的健康之旅。无论是饮食营养、健身锻炼还是生活管理,我都能为你提供专业建议。`
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -215,7 +216,7 @@ export default function CoachScreen() {
|
|||||||
messages: [
|
messages: [
|
||||||
`${timeGreeting},${name}!午餐时间很关键呢,合理的营养搭配能为下午提供充足能量。我是${botName},可以为你分析饮食营养和热量管理。`,
|
`${timeGreeting},${name}!午餐时间很关键呢,合理的营养搭配能为下午提供充足能量。我是${botName},可以为你分析饮食营养和热量管理。`,
|
||||||
`${timeGreeting}!忙碌的上午结束了,该关注一下身体需求啦。我是你的健康助手${botName},无论是饮食调整、运动安排还是休息建议,都可以找我。`,
|
`${timeGreeting}!忙碌的上午结束了,该关注一下身体需求啦。我是你的健康助手${botName},无论是饮食调整、运动安排还是休息建议,都可以找我。`,
|
||||||
`${timeGreeting},${name}!午间是调整状态的好时机。作为你的海豹助手,我建议关注饮食均衡和适度放松~`
|
`${timeGreeting},${name}!午间是调整状态的好时机。作为你的Seal,我建议关注饮食均衡和适度放松~`
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -231,7 +232,7 @@ export default function CoachScreen() {
|
|||||||
messages: [
|
messages: [
|
||||||
`${timeGreeting},${name}!忙碌了一天,现在是时候关注身心平衡了。我是${botName},可以为你提供放松建议、营养补充和恢复方案。`,
|
`${timeGreeting},${name}!忙碌了一天,现在是时候关注身心平衡了。我是${botName},可以为你提供放松建议、营养补充和恢复方案。`,
|
||||||
`${timeGreeting}!夜幕降临,这是一天中最适合总结和调整的时刻。我是你的健康伙伴${botName},让我们一起回顾今天的健康表现,规划明天的目标。`,
|
`${timeGreeting}!夜幕降临,这是一天中最适合总结和调整的时刻。我是你的健康伙伴${botName},让我们一起回顾今天的健康表现,规划明天的目标。`,
|
||||||
`${timeGreeting},${name}!晚间时光属于你自己,也是关爱身体的珍贵时间。作为你的海豹助手,我想陪你聊聊如何更好地管理健康生活。`
|
`${timeGreeting},${name}!晚间时光属于你自己,也是关爱身体的珍贵时间。作为你的Seal,我想陪你聊聊如何更好地管理健康生活。`
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -252,7 +253,7 @@ export default function CoachScreen() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
condition: () => userProfile && (!userProfile.pilatesPurposes || userProfile.pilatesPurposes.length === 0),
|
condition: () => userProfile && (!userProfile.pilatesPurposes || userProfile.pilatesPurposes.length === 0),
|
||||||
message: `${timeGreeting},${name}!作为你的海豹助手,我想更好地了解你的健康需求。告诉我你希望在营养摄入、身材管理、健身锻炼或生活管理方面实现什么目标吧~`
|
message: `${timeGreeting},${name}!作为你的Seal,我想更好地了解你的健康需求。告诉我你希望在营养摄入、身材管理、健身锻炼或生活管理方面实现什么目标吧~`
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -1236,7 +1237,7 @@ export default function CoachScreen() {
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Text style={{ color: '#687076', fontSize: 12 }}>选择合适的方式记录您的饮食,海豹助手会根据您的饮食情况给出专业的营养建议。</Text>
|
<Text style={{ color: '#687076', fontSize: 12 }}>选择合适的方式记录您的饮食,Seal会根据您的饮食情况给出专业的营养建议。</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1278,7 +1279,7 @@ export default function CoachScreen() {
|
|||||||
<Text style={{ color: '#192126', fontWeight: '700' }}>发送记录</Text>
|
<Text style={{ color: '#192126', fontWeight: '700' }}>发送记录</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
<Text style={{ color: '#687076', fontSize: 12 }}>详细描述您的饮食内容和分量,有助于海豹助手给出更精准的营养分析和建议。</Text>
|
<Text style={{ color: '#687076', fontSize: 12 }}>详细描述您的饮食内容和分量,有助于Seal给出更精准的营养分析和建议。</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1825,7 +1826,32 @@ export default function CoachScreen() {
|
|||||||
if (h && Math.abs(h - headerHeight) > 0.5) setHeaderHeight(h);
|
if (h && Math.abs(h - headerHeight) > 0.5) setHeaderHeight(h);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text style={[styles.headerTitle, { color: theme.text }]}>{botName}</Text>
|
<View style={styles.headerLeft}>
|
||||||
|
<Text style={[styles.headerTitle, { color: theme.text }]}>{botName}</Text>
|
||||||
|
|
||||||
|
{/* 使用次数显示 */}
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.usageCountContainer}
|
||||||
|
onPress={() => {
|
||||||
|
// 临时测试:切换VIP状态
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
dispatch(updateProfile({
|
||||||
|
isVip: !userProfile?.isVip,
|
||||||
|
freeUsageCount: userProfile?.isVip ? 3 : 5,
|
||||||
|
maxUsageCount: userProfile?.isVip ? 5 : 10
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
source={require('@/assets/images/icons/iconFlash.png')}
|
||||||
|
style={styles.usageIcon}
|
||||||
|
/>
|
||||||
|
<Text style={[styles.usageText, { color: theme.text }]}>
|
||||||
|
{userProfile?.isVip ? '不限' : `${userProfile?.freeUsageCount || 0}/${userProfile?.maxUsageCount || 0}`}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
<View style={styles.headerActions}>
|
<View style={styles.headerActions}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
@@ -2083,6 +2109,11 @@ const styles = StyleSheet.create({
|
|||||||
paddingHorizontal: 16,
|
paddingHorizontal: 16,
|
||||||
paddingBottom: 10,
|
paddingBottom: 10,
|
||||||
},
|
},
|
||||||
|
headerLeft: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 12,
|
||||||
|
},
|
||||||
headerTitle: {
|
headerTitle: {
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: '800',
|
fontWeight: '800',
|
||||||
@@ -2809,6 +2840,24 @@ const styles = StyleSheet.create({
|
|||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
color: '#FFFFFF',
|
color: '#FFFFFF',
|
||||||
},
|
},
|
||||||
|
usageCountContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 4,
|
||||||
|
paddingHorizontal: 8,
|
||||||
|
paddingVertical: 4,
|
||||||
|
borderRadius: 12,
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.06)',
|
||||||
|
},
|
||||||
|
usageIcon: {
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
},
|
||||||
|
usageText: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#687076',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const markdownStyles = {
|
const markdownStyles = {
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import { useColorScheme } from '@/hooks/useColorScheme';
|
|||||||
import { calculateNutritionSummary, getDietRecords, NutritionSummary } from '@/services/dietRecords';
|
import { calculateNutritionSummary, getDietRecords, NutritionSummary } from '@/services/dietRecords';
|
||||||
import { getMonthDaysZh, getMonthTitleZh, getTodayIndexInMonth } from '@/utils/date';
|
import { getMonthDaysZh, getMonthTitleZh, getTodayIndexInMonth } from '@/utils/date';
|
||||||
import { ensureHealthPermissions, fetchHealthDataForDate } from '@/utils/health';
|
import { ensureHealthPermissions, fetchHealthDataForDate } from '@/utils/health';
|
||||||
import { Ionicons } from '@expo/vector-icons';
|
|
||||||
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
|
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
|
||||||
import { useFocusEffect } from '@react-navigation/native';
|
import { useFocusEffect } from '@react-navigation/native';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
@@ -271,15 +270,29 @@ export default function ExploreScreen() {
|
|||||||
>
|
>
|
||||||
{days.map((d, i) => {
|
{days.map((d, i) => {
|
||||||
const selected = i === selectedIndex;
|
const selected = i === selectedIndex;
|
||||||
|
const isFutureDate = d.date.isAfter(dayjs(), 'day');
|
||||||
return (
|
return (
|
||||||
<View key={`${d.dayOfMonth}`} style={styles.dayItemWrapper}>
|
<View key={`${d.dayOfMonth}`} style={styles.dayItemWrapper}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[styles.dayPill, selected ? styles.dayPillSelected : styles.dayPillNormal]}
|
style={[
|
||||||
onPress={() => onSelectDate(i)}
|
styles.dayPill,
|
||||||
activeOpacity={0.8}
|
selected ? styles.dayPillSelected : styles.dayPillNormal,
|
||||||
|
isFutureDate && styles.dayPillDisabled
|
||||||
|
]}
|
||||||
|
onPress={() => !isFutureDate && onSelectDate(i)}
|
||||||
|
activeOpacity={isFutureDate ? 1 : 0.8}
|
||||||
|
disabled={isFutureDate}
|
||||||
>
|
>
|
||||||
<Text style={[styles.dayLabel, selected && styles.dayLabelSelected]}> {d.weekdayZh} </Text>
|
<Text style={[
|
||||||
<Text style={[styles.dayDate, selected && styles.dayDateSelected]}>{d.dayOfMonth}</Text>
|
styles.dayLabel,
|
||||||
|
selected && styles.dayLabelSelected,
|
||||||
|
isFutureDate && styles.dayLabelDisabled
|
||||||
|
]}> {d.weekdayZh} </Text>
|
||||||
|
<Text style={[
|
||||||
|
styles.dayDate,
|
||||||
|
selected && styles.dayDateSelected,
|
||||||
|
isFutureDate && styles.dayDateDisabled
|
||||||
|
]}>{d.dayOfMonth}</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
{selected && <View style={styles.selectedDot} />}
|
{selected && <View style={styles.selectedDot} />}
|
||||||
</View>
|
</View>
|
||||||
@@ -319,9 +332,6 @@ export default function ExploreScreen() {
|
|||||||
|
|
||||||
<View style={[styles.masonryCard, styles.stepsCard]}>
|
<View style={[styles.masonryCard, styles.stepsCard]}>
|
||||||
<View style={styles.cardHeaderRow}>
|
<View style={styles.cardHeaderRow}>
|
||||||
<View style={styles.iconSquare}>
|
|
||||||
<Ionicons name="footsteps-outline" size={18} color="#192126" />
|
|
||||||
</View>
|
|
||||||
<Text style={styles.cardTitle}>步数</Text>
|
<Text style={styles.cardTitle}>步数</Text>
|
||||||
</View>
|
</View>
|
||||||
{stepCount != null ? (
|
{stepCount != null ? (
|
||||||
@@ -367,9 +377,6 @@ export default function ExploreScreen() {
|
|||||||
|
|
||||||
<View style={[styles.masonryCard, styles.sleepCard]}>
|
<View style={[styles.masonryCard, styles.sleepCard]}>
|
||||||
<View style={styles.cardHeaderRow}>
|
<View style={styles.cardHeaderRow}>
|
||||||
<View style={styles.iconSquare}>
|
|
||||||
<Ionicons name="moon-outline" size={18} color="#192126" />
|
|
||||||
</View>
|
|
||||||
<Text style={styles.cardTitle}>睡眠</Text>
|
<Text style={styles.cardTitle}>睡眠</Text>
|
||||||
</View>
|
</View>
|
||||||
{sleepDuration != null ? (
|
{sleepDuration != null ? (
|
||||||
@@ -443,6 +450,10 @@ const styles = StyleSheet.create({
|
|||||||
dayPillSelected: {
|
dayPillSelected: {
|
||||||
backgroundColor: lightColors.datePickerSelected,
|
backgroundColor: lightColors.datePickerSelected,
|
||||||
},
|
},
|
||||||
|
dayPillDisabled: {
|
||||||
|
backgroundColor: '#F5F5F5',
|
||||||
|
opacity: 0.5,
|
||||||
|
},
|
||||||
dayLabel: {
|
dayLabel: {
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
@@ -452,6 +463,9 @@ const styles = StyleSheet.create({
|
|||||||
dayLabelSelected: {
|
dayLabelSelected: {
|
||||||
color: '#FFFFFF',
|
color: '#FFFFFF',
|
||||||
},
|
},
|
||||||
|
dayLabelDisabled: {
|
||||||
|
color: '#9AA3AE',
|
||||||
|
},
|
||||||
dayDate: {
|
dayDate: {
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: '800',
|
fontWeight: '800',
|
||||||
@@ -460,6 +474,9 @@ const styles = StyleSheet.create({
|
|||||||
dayDateSelected: {
|
dayDateSelected: {
|
||||||
color: '#FFFFFF',
|
color: '#FFFFFF',
|
||||||
},
|
},
|
||||||
|
dayDateDisabled: {
|
||||||
|
color: '#9AA3AE',
|
||||||
|
},
|
||||||
selectedDot: {
|
selectedDot: {
|
||||||
width: 5,
|
width: 5,
|
||||||
height: 5,
|
height: 5,
|
||||||
|
|||||||
BIN
assets/images/icons/iconFlash.png
Normal file
BIN
assets/images/icons/iconFlash.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
@@ -56,7 +56,7 @@ export function StressAnalysisModal({ visible, onClose, hrvValue, updateTime }:
|
|||||||
<View style={styles.chartContainer}>
|
<View style={styles.chartContainer}>
|
||||||
<View style={styles.colorBar}>
|
<View style={styles.colorBar}>
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
colors={['#10B981', '#3B82F6', '#F59E0B']}
|
colors={['#F59E0B', '#3B82F6', '#10B981']}
|
||||||
start={{ x: 0, y: 0 }}
|
start={{ x: 0, y: 0 }}
|
||||||
end={{ x: 1, y: 0 }}
|
end={{ x: 1, y: 0 }}
|
||||||
style={styles.gradientBar}
|
style={styles.gradientBar}
|
||||||
@@ -64,16 +64,16 @@ export function StressAnalysisModal({ visible, onClose, hrvValue, updateTime }:
|
|||||||
</View>
|
</View>
|
||||||
<View style={styles.legend}>
|
<View style={styles.legend}>
|
||||||
<View style={styles.legendItem}>
|
<View style={styles.legendItem}>
|
||||||
<View style={[styles.legendDot, { backgroundColor: '#10B981' }]} />
|
<View style={[styles.legendDot, { backgroundColor: '#F59E0B' }]} />
|
||||||
<Text style={styles.legendText}>好事发生</Text>
|
<Text style={styles.legendText}>鸭梨山大</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.legendItem}>
|
<View style={styles.legendItem}>
|
||||||
<View style={[styles.legendDot, { backgroundColor: '#3B82F6' }]} />
|
<View style={[styles.legendDot, { backgroundColor: '#3B82F6' }]} />
|
||||||
<Text style={styles.legendText}>活力满满</Text>
|
<Text style={styles.legendText}>活力满满</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.legendItem}>
|
<View style={styles.legendItem}>
|
||||||
<View style={[styles.legendDot, { backgroundColor: '#F59E0B' }]} />
|
<View style={[styles.legendDot, { backgroundColor: '#10B981' }]} />
|
||||||
<Text style={styles.legendText}>鸭梨山大</Text>
|
<Text style={styles.legendText}>好事发生</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { Ionicons } from '@expo/vector-icons';
|
|
||||||
import { LinearGradient } from 'expo-linear-gradient';
|
import { LinearGradient } from 'expo-linear-gradient';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||||
@@ -19,9 +18,9 @@ export function StressMeter({ value, updateTime, style, hrvValue }: StressMeterP
|
|||||||
const getStressStatus = () => {
|
const getStressStatus = () => {
|
||||||
if (value === null) {
|
if (value === null) {
|
||||||
return '未知';
|
return '未知';
|
||||||
} else if (value <= 30) {
|
} else if (value >= 70) {
|
||||||
return '放松';
|
return '放松';
|
||||||
} else if (value <= 70) {
|
} else if (value >= 30) {
|
||||||
return '正常';
|
return '正常';
|
||||||
} else {
|
} else {
|
||||||
return '紧张';
|
return '紧张';
|
||||||
@@ -46,7 +45,7 @@ export function StressMeter({ value, updateTime, style, hrvValue }: StressMeterP
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 计算进度条位置(0-100%)
|
// 计算进度条位置(0-100%)
|
||||||
// 压力指数越高,进度条越满
|
// 压力指数越高,进度条越满(红色区域越多)
|
||||||
const progressPercentage = value !== null ? Math.max(0, Math.min(100, value)) : 0;
|
const progressPercentage = value !== null ? Math.max(0, Math.min(100, value)) : 0;
|
||||||
|
|
||||||
// 在组件内部添加状态
|
// 在组件内部添加状态
|
||||||
@@ -67,9 +66,6 @@ export function StressMeter({ value, updateTime, style, hrvValue }: StressMeterP
|
|||||||
{/* 头部区域 */}
|
{/* 头部区域 */}
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
<View style={styles.leftSection}>
|
<View style={styles.leftSection}>
|
||||||
<View style={styles.iconContainer}>
|
|
||||||
<Ionicons name="heart" size={16} color="red" />
|
|
||||||
</View>
|
|
||||||
<Text style={styles.title}>压力</Text>
|
<Text style={styles.title}>压力</Text>
|
||||||
</View>
|
</View>
|
||||||
<Text style={styles.emoji}>{getStatusEmoji()}</Text>
|
<Text style={styles.emoji}>{getStatusEmoji()}</Text>
|
||||||
@@ -87,7 +83,7 @@ export function StressMeter({ value, updateTime, style, hrvValue }: StressMeterP
|
|||||||
{/* 渐变背景进度条 */}
|
{/* 渐变背景进度条 */}
|
||||||
<View style={[styles.progressBar, { width: '100%' }]}>
|
<View style={[styles.progressBar, { width: '100%' }]}>
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
colors={['#10B981', '#FCD34D', '#F97316']}
|
colors={['#10B981', '#FCD34D', '#EF4444']}
|
||||||
start={{ x: 0, y: 0 }}
|
start={{ x: 0, y: 0 }}
|
||||||
end={{ x: 1, y: 0 }}
|
end={{ x: 1, y: 0 }}
|
||||||
style={styles.gradientBar}
|
style={styles.gradientBar}
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ export type UserProfile = {
|
|||||||
dailyStepsGoal?: number; // 每日步数目标(用于 Explore 页等)
|
dailyStepsGoal?: number; // 每日步数目标(用于 Explore 页等)
|
||||||
dailyCaloriesGoal?: number; // 每日卡路里消耗目标
|
dailyCaloriesGoal?: number; // 每日卡路里消耗目标
|
||||||
pilatesPurposes?: string[]; // 普拉提目的(多选)
|
pilatesPurposes?: string[]; // 普拉提目的(多选)
|
||||||
|
isVip?: boolean;
|
||||||
|
freeUsageCount?: number;
|
||||||
|
maxUsageCount?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UserState = {
|
export type UserState = {
|
||||||
@@ -38,6 +41,9 @@ const initialState: UserState = {
|
|||||||
token: null,
|
token: null,
|
||||||
profile: {
|
profile: {
|
||||||
name: DEFAULT_MEMBER_NAME,
|
name: DEFAULT_MEMBER_NAME,
|
||||||
|
isVip: false,
|
||||||
|
freeUsageCount: 3,
|
||||||
|
maxUsageCount: 5,
|
||||||
},
|
},
|
||||||
loading: false,
|
loading: false,
|
||||||
error: null,
|
error: null,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export function getChineseGreeting(now: Date = new Date()): string {
|
|||||||
|
|
||||||
/** 获取中文月份标题,例如:2025年8月 */
|
/** 获取中文月份标题,例如:2025年8月 */
|
||||||
export function getMonthTitleZh(date: Dayjs = dayjs()): string {
|
export function getMonthTitleZh(date: Dayjs = dayjs()): string {
|
||||||
return date.format('YYYY年M月');
|
return date.format('YY年M月');
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MonthDay = {
|
export type MonthDay = {
|
||||||
|
|||||||
Reference in New Issue
Block a user