feat(ui): 统一健康卡片标题图标并优化语音录音稳定性
- 为所有健康数据卡片添加对应功能图标,提升视觉一致性 - 将“小鱼干”文案统一为“能量值”,并更新获取说明 - 语音录音页面增加组件卸载保护、错误提示与资源清理逻辑 - 个人页支持毛玻璃按钮样式,默认用户名置空 - 新增血氧、饮食、心情、压力、睡眠、步数、体重等图标资源 - 升级 react-native-purchases 至 9.4.3 - 移除 useAuthGuard 调试日志
This commit is contained in:
@@ -184,23 +184,23 @@ const ActivityHeatMap = () => {
|
||||
>
|
||||
<View style={[styles.popoverContent, { backgroundColor: colors.card }]}>
|
||||
<Text style={[styles.popoverTitle, { color: colors.text }]}>
|
||||
小鱼干可以用来与小海豹进行对话
|
||||
能量值的积攒后续可以用来兑换 AI 相关权益
|
||||
</Text>
|
||||
<Text style={[styles.popoverSubtitle, { color: colors.text }]}>
|
||||
获取说明
|
||||
</Text>
|
||||
<View style={styles.popoverList}>
|
||||
<Text style={[styles.popoverItem, { color: colors.textMuted }]}>
|
||||
1. 每日登录获得小鱼干+1
|
||||
1. 每日登录获得能量值+1
|
||||
</Text>
|
||||
<Text style={[styles.popoverItem, { color: colors.textMuted }]}>
|
||||
2. 每日记录心情获得小鱼干+1
|
||||
2. 每日记录心情获得能量值+1
|
||||
</Text>
|
||||
<Text style={[styles.popoverItem, { color: colors.textMuted }]}>
|
||||
3. 记饮食获得小鱼干+1
|
||||
3. 记饮食获得能量值+1
|
||||
</Text>
|
||||
<Text style={[styles.popoverItem, { color: colors.textMuted }]}>
|
||||
4. 完成一次目标获得小鱼干+1
|
||||
4. 完成一次目标获得能量值+1
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AnimatedNumber } from '@/components/AnimatedNumber';
|
||||
import { Image } from 'expo-image';
|
||||
import React from 'react';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
@@ -35,6 +36,10 @@ export function BasalMetabolismCard({ value, resetToken, style }: BasalMetabolis
|
||||
{/* 头部区域 */}
|
||||
<View style={styles.header}>
|
||||
<View style={styles.leftSection}>
|
||||
<Image
|
||||
source={require('@/assets/images/icons/icon-fire.png')}
|
||||
style={styles.titleIcon}
|
||||
/>
|
||||
<Text style={styles.title}>基础代谢</Text>
|
||||
</View>
|
||||
<View style={[styles.statusBadge, { backgroundColor: status.color + '20' }]}>
|
||||
@@ -117,6 +122,12 @@ const styles = StyleSheet.create({
|
||||
title: {
|
||||
fontSize: 14,
|
||||
color: '#0F172A',
|
||||
fontWeight: '600',
|
||||
},
|
||||
titleIcon: {
|
||||
width: 16,
|
||||
height: 16,
|
||||
marginRight: 4,
|
||||
},
|
||||
statusBadge: {
|
||||
paddingHorizontal: 8,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { MoodCheckin, getMoodConfig } from '@/services/moodCheckins';
|
||||
import dayjs from 'dayjs';
|
||||
import LottieView from 'lottie-react-native';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
|
||||
interface MoodCardProps {
|
||||
moodCheckin: MoodCheckin | null;
|
||||
@@ -23,7 +23,13 @@ export function MoodCard({ moodCheckin, onPress }: MoodCardProps) {
|
||||
return (
|
||||
<TouchableOpacity onPress={onPress} style={styles.moodCardContent} >
|
||||
<View style={styles.moodCardHeader}>
|
||||
<Text style={styles.cardTitle}>心情</Text>
|
||||
<View style={styles.titleContainer}>
|
||||
<Image
|
||||
source={require('@/assets/images/icons/icon-mood.png')}
|
||||
style={styles.titleIcon}
|
||||
/>
|
||||
<Text style={styles.cardTitle}>心情</Text>
|
||||
</View>
|
||||
<LottieView
|
||||
ref={animationRef}
|
||||
source={require('@/assets/lottie/mood/mood_demo.json')}
|
||||
@@ -59,9 +65,22 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
titleContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
titleIcon: {
|
||||
width: 16,
|
||||
height: 16,
|
||||
marginRight: 6,
|
||||
resizeMode: 'contain',
|
||||
},
|
||||
|
||||
cardTitle: {
|
||||
fontSize: 14,
|
||||
color: '#192126',
|
||||
fontWeight: '600'
|
||||
},
|
||||
|
||||
lottieAnimation: {
|
||||
|
||||
@@ -140,7 +140,13 @@ export function NutritionRadarCard({
|
||||
return (
|
||||
<TouchableOpacity style={styles.card} onPress={handleNavigateToRecords} activeOpacity={0.8}>
|
||||
<View style={styles.cardHeader}>
|
||||
<Text style={styles.cardTitle}>饮食分析</Text>
|
||||
<View style={styles.titleContainer}>
|
||||
<Image
|
||||
source={require('@/assets/images/icons/icon-healthy-diet.png')}
|
||||
style={styles.titleIcon}
|
||||
/>
|
||||
<Text style={styles.cardTitle}>饮食分析</Text>
|
||||
</View>
|
||||
<Text style={styles.cardSubtitle}>更新: {dayjs(nutritionSummary?.updatedAt).format('MM-DD HH:mm')}</Text>
|
||||
</View>
|
||||
|
||||
@@ -292,9 +298,20 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
marginBottom: 8,
|
||||
},
|
||||
titleContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
titleIcon: {
|
||||
width: 16,
|
||||
height: 16,
|
||||
marginRight: 6,
|
||||
resizeMode: 'contain',
|
||||
},
|
||||
cardTitle: {
|
||||
fontSize: 14,
|
||||
color: '#192126',
|
||||
fontWeight: '600'
|
||||
},
|
||||
cardSubtitle: {
|
||||
fontSize: 10,
|
||||
|
||||
@@ -10,9 +10,10 @@ import {
|
||||
|
||||
import { fetchHourlyStepSamples, fetchStepCount, HourlyStepData } from '@/utils/health';
|
||||
import { logger } from '@/utils/logger';
|
||||
import dayjs from 'dayjs';
|
||||
import { Image } from 'expo-image';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { AnimatedNumber } from './AnimatedNumber';
|
||||
import dayjs from 'dayjs';
|
||||
// 使用原生View来替代SVG,避免导入问题
|
||||
// import Svg, { Rect } from 'react-native-svg';
|
||||
|
||||
@@ -110,6 +111,10 @@ const StepsCard: React.FC<StepsCardProps> = ({
|
||||
<>
|
||||
{/* 标题和步数显示 */}
|
||||
<View style={styles.header}>
|
||||
<Image
|
||||
source={require('@/assets/images/icons/icon-step.png')}
|
||||
style={styles.titleIcon}
|
||||
/>
|
||||
<Text style={styles.title}>步数</Text>
|
||||
</View>
|
||||
|
||||
@@ -213,12 +218,19 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
},
|
||||
titleIcon: {
|
||||
width: 16,
|
||||
height: 16,
|
||||
marginRight: 6,
|
||||
resizeMode: 'contain',
|
||||
},
|
||||
title: {
|
||||
fontSize: 14,
|
||||
color: '#192126',
|
||||
fontWeight: '600'
|
||||
},
|
||||
footprintIcons: {
|
||||
flexDirection: 'row',
|
||||
@@ -228,6 +240,7 @@ const styles = StyleSheet.create({
|
||||
chartContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
marginTop: 6
|
||||
},
|
||||
chartWrapper: {
|
||||
width: '100%',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { fetchHRVForDate } from '@/utils/health';
|
||||
import dayjs from 'dayjs';
|
||||
import { Image } from 'expo-image';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
@@ -96,6 +97,10 @@ export function StressMeter({ curDate }: StressMeterProps) {
|
||||
{/* 头部区域 */}
|
||||
<View style={styles.header}>
|
||||
<View style={styles.leftSection}>
|
||||
<Image
|
||||
source={require('@/assets/images/icons/icon-pressure.png')}
|
||||
style={styles.titleIcon}
|
||||
/>
|
||||
<Text style={styles.title}>压力</Text>
|
||||
</View>
|
||||
{/* {updateTime && (
|
||||
@@ -172,9 +177,16 @@ const styles = StyleSheet.create({
|
||||
justifyContent: 'center',
|
||||
marginRight: 6,
|
||||
},
|
||||
titleIcon: {
|
||||
width: 16,
|
||||
height: 16,
|
||||
marginRight: 6,
|
||||
resizeMode: 'contain',
|
||||
},
|
||||
title: {
|
||||
fontSize: 14,
|
||||
color: '#192126',
|
||||
fontWeight: '600'
|
||||
},
|
||||
valueSection: {
|
||||
flexDirection: 'row',
|
||||
|
||||
@@ -4,6 +4,7 @@ import { getQuickWaterAmount } from '@/utils/userPreferences';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import dayjs from 'dayjs';
|
||||
import * as Haptics from 'expo-haptics';
|
||||
import { Image } from 'expo-image';
|
||||
import { useRouter } from 'expo-router';
|
||||
import LottieView from 'lottie-react-native';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
@@ -185,7 +186,16 @@ const WaterIntakeCard: React.FC<WaterIntakeCardProps> = ({
|
||||
|
||||
{/* 标题和加号按钮 */}
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.title}>喝水</Text>
|
||||
<View style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
<Image
|
||||
source={require('@/assets/images/icons/IconGlass.png')}
|
||||
style={styles.titleIcon}
|
||||
/>
|
||||
<Text style={styles.title}>喝水</Text>
|
||||
</View>
|
||||
{isToday && (
|
||||
<TouchableOpacity style={styles.addButton} onPress={handleQuickAddWater}>
|
||||
<Text style={styles.addButtonText}>+ {quickWaterAmount}ml</Text>
|
||||
@@ -291,10 +301,16 @@ const styles = StyleSheet.create({
|
||||
marginBottom: 4,
|
||||
minHeight: 22,
|
||||
},
|
||||
titleIcon: {
|
||||
width: 16,
|
||||
height: 16,
|
||||
marginRight: 6,
|
||||
resizeMode: 'contain',
|
||||
},
|
||||
title: {
|
||||
fontSize: 14,
|
||||
color: '#192126',
|
||||
fontWeight: '500',
|
||||
fontWeight: '600',
|
||||
},
|
||||
addButton: {
|
||||
borderRadius: 16,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Image } from 'expo-image';
|
||||
import React from 'react';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated';
|
||||
@@ -22,7 +23,17 @@ const HealthDataCard: React.FC<HealthDataCardProps> = ({
|
||||
style={[styles.card, style]}
|
||||
>
|
||||
<View style={styles.content}>
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
<View style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: 14,
|
||||
}}>
|
||||
<Image
|
||||
source={require('@/assets/images/icons/icon-blood-oxygen.png')}
|
||||
style={styles.titleIcon}
|
||||
/>
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
</View>
|
||||
<View style={styles.valueContainer}>
|
||||
<Text style={styles.value}>{value}</Text>
|
||||
<Text style={styles.unit}>{unit}</Text>
|
||||
@@ -51,10 +62,16 @@ const styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
titleIcon: {
|
||||
width: 16,
|
||||
height: 16,
|
||||
marginRight: 6,
|
||||
resizeMode: 'contain',
|
||||
},
|
||||
title: {
|
||||
fontSize: 14,
|
||||
color: '#192126',
|
||||
marginBottom: 14,
|
||||
fontWeight: '600',
|
||||
},
|
||||
valueContainer: {
|
||||
flexDirection: 'row',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { fetchCompleteSleepData, formatSleepTime } from '@/utils/sleepHealthKit';
|
||||
import { Image } from 'expo-image';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
|
||||
@@ -39,6 +40,10 @@ const SleepCard: React.FC<SleepCardProps> = ({
|
||||
const CardContent = (
|
||||
<View style={[styles.container, style]}>
|
||||
<View style={styles.cardHeaderRow}>
|
||||
<Image
|
||||
source={require('@/assets/images/icons/icon-sleep.png')}
|
||||
style={styles.titleIcon}
|
||||
/>
|
||||
<Text style={styles.cardTitle}>睡眠</Text>
|
||||
</View>
|
||||
<Text style={styles.sleepValue}>
|
||||
@@ -65,11 +70,18 @@ const styles = StyleSheet.create({
|
||||
cardHeaderRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
justifyContent: 'flex-start',
|
||||
},
|
||||
titleIcon: {
|
||||
width: 16,
|
||||
height: 16,
|
||||
marginRight: 6,
|
||||
resizeMode: 'contain',
|
||||
},
|
||||
cardTitle: {
|
||||
fontSize: 14,
|
||||
color: '#192126',
|
||||
fontWeight: '600',
|
||||
},
|
||||
sleepValue: {
|
||||
fontSize: 16,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import { fetchWeightHistory } from '@/store/userSlice';
|
||||
import { BMI_CATEGORIES } from '@/utils/bmi';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { Image } from 'expo-image';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
@@ -72,6 +73,10 @@ export function WeightHistoryCard() {
|
||||
return (
|
||||
<TouchableOpacity style={styles.card} onPress={navigateToWeightRecords} activeOpacity={0.8}>
|
||||
<View style={styles.cardHeader}>
|
||||
<Image
|
||||
source={require('@/assets/images/icons/icon-weight.png')}
|
||||
style={styles.iconSquare}
|
||||
/>
|
||||
<Text style={styles.cardTitle}>体重记录</Text>
|
||||
</View>
|
||||
|
||||
@@ -156,6 +161,10 @@ export function WeightHistoryCard() {
|
||||
return (
|
||||
<TouchableOpacity style={styles.card} onPress={navigateToWeightRecords} activeOpacity={0.8}>
|
||||
<View style={styles.cardHeader}>
|
||||
<Image
|
||||
source={require('@/assets/images/icons/icon-weight.png')}
|
||||
style={styles.iconSquare}
|
||||
/>
|
||||
<Text style={styles.cardTitle}>体重记录</Text>
|
||||
<TouchableOpacity
|
||||
style={styles.addButton}
|
||||
@@ -345,17 +354,18 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
},
|
||||
iconSquare: {
|
||||
width: 30,
|
||||
height: 30,
|
||||
width: 14,
|
||||
height: 14,
|
||||
borderRadius: 8,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginRight: 2,
|
||||
marginRight: 4,
|
||||
},
|
||||
cardTitle: {
|
||||
fontSize: 14,
|
||||
color: '#192126',
|
||||
flex: 1,
|
||||
fontWeight: '600'
|
||||
},
|
||||
headerButtons: {
|
||||
flexDirection: 'row',
|
||||
|
||||
Reference in New Issue
Block a user