feat: 更新教练页面和布局,优化用户体验

- 将教练页面中的“Bot”名称更改为“Seal”,提升品牌一致性
- 在布局文件中调整标签标题和图标,确保与新名称一致
- 新增使用次数显示功能,优化用户对使用情况的了解
- 更新日期选择器样式,增强未来日期的禁用效果
- 优化压力分析模态框的颜色和文本,提升可读性
This commit is contained in:
richarjiang
2025-08-21 10:29:12 +08:00
parent 78620f18ee
commit b396a7d101
8 changed files with 108 additions and 39 deletions

View File

@@ -43,7 +43,7 @@ export default function TabLayout() {
case 'explore':
return { icon: 'magnifyingglass.circle.fill', title: '发现' } as const;
case 'coach':
return { icon: 'person.3.fill', title: 'Bot' } as const;
return { icon: 'person.3.fill', title: 'Seal' } as const;
case 'statistics':
return { icon: 'chart.pie.fill', title: '统计' } as const;
case 'personal':
@@ -131,7 +131,7 @@ export default function TabLayout() {
<Tabs.Screen
name="coach"
options={{
title: 'Bot',
title: 'Seal',
tabBarIcon: ({ color }) => {
const isCoachSelected = pathname === '/coach';
return (
@@ -148,7 +148,7 @@ export default function TabLayout() {
textAlign: 'center',
flexShrink: 0,
}}>
Bot
Seal
</Text>
)}
</View>
@@ -160,6 +160,7 @@ export default function TabLayout() {
name="explore"
options={{
title: '发现',
href: null,
tabBarIcon: ({ color }) => {
const isHomeSelected = pathname === '/' || pathname === '/index';
return (

View File

@@ -25,12 +25,13 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { Colors } from '@/constants/Colors';
import { getTabBarBottomPadding } from '@/constants/TabBar';
import { useAppSelector } from '@/hooks/redux';
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
import { useAuthGuard } from '@/hooks/useAuthGuard';
import { useCosUpload } from '@/hooks/useCosUpload';
import { deleteConversation, getConversationDetail, listConversations, type AiConversationListItem } from '@/services/aiCoach';
import { loadAiCoachSessionCache, saveAiCoachSessionCache } from '@/services/aiCoachSession';
import { api, getAuthToken, postTextStream } from '@/services/api';
import { updateProfile } from '@/store/userSlice';
import dayjs from 'dayjs';
import { LinearGradient } from 'expo-linear-gradient';
import { ActionSheet } from '../../components/ui/ActionSheet';
@@ -124,7 +125,7 @@ export default function CoachScreen() {
const { isLoggedIn, pushIfAuthedElseLogin } = useAuthGuard();
// 为了让页面更贴近品牌主题与更亮的观感,这里使用亮色系配色
const theme = Colors.light;
const botName = (params?.name || 'Bot').toString();
const botName = (params?.name || 'Seal').toString();
const [input, setInput] = useState('');
const [isSending, setIsSending] = useState(false);
const [isStreaming, setIsStreaming] = useState(false);
@@ -174,7 +175,7 @@ export default function CoachScreen() {
const generateWelcomeMessage = useCallback(() => {
const hour = new Date().getHours();
const name = userProfile?.name || '朋友';
const botName = (params?.name || '海豹助手').toString();
const botName = (params?.name || 'Seal').toString();
// 时段问候
let timeGreeting = '';
@@ -199,7 +200,7 @@ export default function CoachScreen() {
messages: [
`${timeGreeting}${name}!我是${botName},你的专属健康管理助手。新的一天开始了,让我们一起为你的健康目标努力吧!`,
`${timeGreeting}!早晨是制定健康计划的最佳时机,我是${botName},可以帮你管理营养摄入、运动计划和生活作息。`,
`${timeGreeting}${name}!作为你的海豹助手,我很高兴能陪伴你的健康之旅。无论是饮食营养、健身锻炼还是生活管理,我都能为你提供专业建议。`
`${timeGreeting}${name}!作为你的Seal,我很高兴能陪伴你的健康之旅。无论是饮食营养、健身锻炼还是生活管理,我都能为你提供专业建议。`
]
},
{
@@ -215,7 +216,7 @@ export default function CoachScreen() {
messages: [
`${timeGreeting}${name}!午餐时间很关键呢,合理的营养搭配能为下午提供充足能量。我是${botName},可以为你分析饮食营养和热量管理。`,
`${timeGreeting}!忙碌的上午结束了,该关注一下身体需求啦。我是你的健康助手${botName},无论是饮食调整、运动安排还是休息建议,都可以找我。`,
`${timeGreeting}${name}!午间是调整状态的好时机。作为你的海豹助手,我建议关注饮食均衡和适度放松~`
`${timeGreeting}${name}!午间是调整状态的好时机。作为你的Seal,我建议关注饮食均衡和适度放松~`
]
},
{
@@ -231,7 +232,7 @@ export default function CoachScreen() {
messages: [
`${timeGreeting}${name}!忙碌了一天,现在是时候关注身心平衡了。我是${botName},可以为你提供放松建议、营养补充和恢复方案。`,
`${timeGreeting}!夜幕降临,这是一天中最适合总结和调整的时刻。我是你的健康伙伴${botName},让我们一起回顾今天的健康表现,规划明天的目标。`,
`${timeGreeting}${name}!晚间时光属于你自己,也是关爱身体的珍贵时间。作为你的海豹助手,我想陪你聊聊如何更好地管理健康生活。`
`${timeGreeting}${name}!晚间时光属于你自己,也是关爱身体的珍贵时间。作为你的Seal,我想陪你聊聊如何更好地管理健康生活。`
]
},
{
@@ -252,7 +253,7 @@ export default function CoachScreen() {
},
{
condition: () => userProfile && (!userProfile.pilatesPurposes || userProfile.pilatesPurposes.length === 0),
message: `${timeGreeting}${name}!作为你的海豹助手,我想更好地了解你的健康需求。告诉我你希望在营养摄入、身材管理、健身锻炼或生活管理方面实现什么目标吧~`
message: `${timeGreeting}${name}!作为你的Seal,我想更好地了解你的健康需求。告诉我你希望在营养摄入、身材管理、健身锻炼或生活管理方面实现什么目标吧~`
}
];
@@ -1236,7 +1237,7 @@ export default function CoachScreen() {
</TouchableOpacity>
</View>
<Text style={{ color: '#687076', fontSize: 12 }}></Text>
<Text style={{ color: '#687076', fontSize: 12 }}>Seal会根据您的饮食情况给出专业的营养建</Text>
</View>
);
}
@@ -1278,7 +1279,7 @@ export default function CoachScreen() {
<Text style={{ color: '#192126', fontWeight: '700' }}></Text>
</TouchableOpacity>
<Text style={{ color: '#687076', fontSize: 12 }}></Text>
<Text style={{ color: '#687076', fontSize: 12 }}>Seal给出更精准的营养分析和建</Text>
</View>
);
}
@@ -1825,7 +1826,32 @@ export default function CoachScreen() {
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}>
<TouchableOpacity
accessibilityRole="button"
@@ -2083,6 +2109,11 @@ const styles = StyleSheet.create({
paddingHorizontal: 16,
paddingBottom: 10,
},
headerLeft: {
flexDirection: 'row',
alignItems: 'center',
gap: 12,
},
headerTitle: {
fontSize: 20,
fontWeight: '800',
@@ -2809,6 +2840,24 @@ const styles = StyleSheet.create({
fontWeight: '700',
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 = {

View File

@@ -13,7 +13,6 @@ import { useColorScheme } from '@/hooks/useColorScheme';
import { calculateNutritionSummary, getDietRecords, NutritionSummary } from '@/services/dietRecords';
import { getMonthDaysZh, getMonthTitleZh, getTodayIndexInMonth } from '@/utils/date';
import { ensureHealthPermissions, fetchHealthDataForDate } from '@/utils/health';
import { Ionicons } from '@expo/vector-icons';
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
import { useFocusEffect } from '@react-navigation/native';
import dayjs from 'dayjs';
@@ -271,15 +270,29 @@ export default function ExploreScreen() {
>
{days.map((d, i) => {
const selected = i === selectedIndex;
const isFutureDate = d.date.isAfter(dayjs(), 'day');
return (
<View key={`${d.dayOfMonth}`} style={styles.dayItemWrapper}>
<TouchableOpacity
style={[styles.dayPill, selected ? styles.dayPillSelected : styles.dayPillNormal]}
onPress={() => onSelectDate(i)}
activeOpacity={0.8}
style={[
styles.dayPill,
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={[styles.dayDate, selected && styles.dayDateSelected]}>{d.dayOfMonth}</Text>
<Text style={[
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>
{selected && <View style={styles.selectedDot} />}
</View>
@@ -319,9 +332,6 @@ export default function ExploreScreen() {
<View style={[styles.masonryCard, styles.stepsCard]}>
<View style={styles.cardHeaderRow}>
<View style={styles.iconSquare}>
<Ionicons name="footsteps-outline" size={18} color="#192126" />
</View>
<Text style={styles.cardTitle}></Text>
</View>
{stepCount != null ? (
@@ -367,9 +377,6 @@ export default function ExploreScreen() {
<View style={[styles.masonryCard, styles.sleepCard]}>
<View style={styles.cardHeaderRow}>
<View style={styles.iconSquare}>
<Ionicons name="moon-outline" size={18} color="#192126" />
</View>
<Text style={styles.cardTitle}></Text>
</View>
{sleepDuration != null ? (
@@ -443,6 +450,10 @@ const styles = StyleSheet.create({
dayPillSelected: {
backgroundColor: lightColors.datePickerSelected,
},
dayPillDisabled: {
backgroundColor: '#F5F5F5',
opacity: 0.5,
},
dayLabel: {
fontSize: 12,
fontWeight: '700',
@@ -452,6 +463,9 @@ const styles = StyleSheet.create({
dayLabelSelected: {
color: '#FFFFFF',
},
dayLabelDisabled: {
color: '#9AA3AE',
},
dayDate: {
fontSize: 12,
fontWeight: '800',
@@ -460,6 +474,9 @@ const styles = StyleSheet.create({
dayDateSelected: {
color: '#FFFFFF',
},
dayDateDisabled: {
color: '#9AA3AE',
},
selectedDot: {
width: 5,
height: 5,