feat: 新增语音记录饮食功能与开发者调试模块
- 集成 @react-native-voice/voice 实现中文语音识别,支持“一句话记录”餐食 - 新增语音录制页面,含波形动画、音量反馈与识别结果确认 - FloatingFoodOverlay 新增语音入口,打通拍照/库/语音三种记录方式 - 添加麦克风与语音识别权限描述(iOS Info.plist 与 Android manifest) - 实现开发者模式:连续三次点击用户名激活,含日志查看、导出与清除 - 新增 logger 工具类,统一日志存储(AsyncStorage)与按级别输出 - 重构 BackgroundTaskManager 为单例并支持 Promise 初始化,避免重复注册 - 移除 sleep-detail 多余渐变背景,改用 ThemedView 统一主题 - 新增通用 haptic 反馈函数,支持多种震动类型(iOS only) - 升级 expo-background-task、expo-notifications、expo-task-manager 至兼容版本
This commit is contained in:
@@ -1,17 +1,19 @@
|
||||
import ActivityHeatMap from '@/components/ActivityHeatMap';
|
||||
import { PRIVACY_POLICY_URL, USER_AGREEMENT_URL } from '@/constants/Agree';
|
||||
import { ROUTES } from '@/constants/Routes';
|
||||
import { getTabBarBottomPadding } from '@/constants/TabBar';
|
||||
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||||
import { useAuthGuard } from '@/hooks/useAuthGuard';
|
||||
import { useNotifications } from '@/hooks/useNotifications';
|
||||
import { DEFAULT_MEMBER_NAME, fetchActivityHistory, fetchMyProfile } from '@/store/userSlice';
|
||||
import { log } from '@/utils/logger';
|
||||
import { getNotificationEnabled, setNotificationEnabled as saveNotificationEnabled } from '@/utils/userPreferences';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import { Image } from 'expo-image';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Alert, Linking, ScrollView, StatusBar, StyleSheet, Switch, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
@@ -31,6 +33,11 @@ export default function PersonalScreen() {
|
||||
|
||||
const [notificationEnabled, setNotificationEnabled] = useState(false);
|
||||
|
||||
// 开发者模式相关状态
|
||||
const [showDeveloperSection, setShowDeveloperSection] = useState(false);
|
||||
const clickTimestamps = useRef<number[]>([]);
|
||||
const clickTimeoutRef = useRef<number | null>(null);
|
||||
|
||||
// 计算底部间距
|
||||
const bottomPadding = useMemo(() => {
|
||||
return getTabBarBottomPadding(tabBarHeight) + (insets?.bottom ?? 0);
|
||||
@@ -86,6 +93,32 @@ export default function PersonalScreen() {
|
||||
loadNotificationPreference();
|
||||
}, []);
|
||||
|
||||
// 处理用户名连续点击
|
||||
const handleUserNamePress = () => {
|
||||
const now = Date.now();
|
||||
clickTimestamps.current.push(now);
|
||||
|
||||
// 清除之前的超时
|
||||
if (clickTimeoutRef.current) {
|
||||
clearTimeout(clickTimeoutRef.current);
|
||||
}
|
||||
|
||||
// 只保留最近1秒内的点击
|
||||
clickTimestamps.current = clickTimestamps.current.filter(timestamp => now - timestamp <= 1000);
|
||||
|
||||
// 检查是否有3次连续点击
|
||||
if (clickTimestamps.current.length >= 3) {
|
||||
setShowDeveloperSection(true);
|
||||
clickTimestamps.current = []; // 清空点击记录
|
||||
log.info('开发者模式已激活');
|
||||
} else {
|
||||
// 1秒后清空点击记录
|
||||
clickTimeoutRef.current = setTimeout(() => {
|
||||
clickTimestamps.current = [];
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理通知开关变化
|
||||
const handleNotificationToggle = async (value: boolean) => {
|
||||
if (value) {
|
||||
@@ -148,7 +181,9 @@ export default function PersonalScreen() {
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.userDetails}>
|
||||
<Text style={styles.userName}>{displayName}</Text>
|
||||
<TouchableOpacity onPress={handleUserNamePress} activeOpacity={0.7}>
|
||||
<Text style={styles.userName}>{displayName}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<TouchableOpacity style={styles.editButton} onPress={() => pushIfAuthedElseLogin('/profile/edit')}>
|
||||
<Text style={styles.editButtonText}>编辑</Text>
|
||||
@@ -238,6 +273,17 @@ export default function PersonalScreen() {
|
||||
},
|
||||
],
|
||||
},
|
||||
// 开发者section(需要连续点击三次用户名激活)
|
||||
...(showDeveloperSection ? [{
|
||||
title: '开发者',
|
||||
items: [
|
||||
{
|
||||
icon: 'code-slash-outline' as const,
|
||||
title: '开发者选项',
|
||||
onPress: () => pushIfAuthedElseLogin(ROUTES.DEVELOPER),
|
||||
},
|
||||
],
|
||||
}] : []),
|
||||
{
|
||||
title: '其他',
|
||||
items: [
|
||||
|
||||
@@ -13,7 +13,7 @@ import { Colors } from '@/constants/Colors';
|
||||
import { getTabBarBottomPadding } from '@/constants/TabBar';
|
||||
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||||
import { useAuthGuard } from '@/hooks/useAuthGuard';
|
||||
import { backgroundTaskManager } from '@/services/backgroundTaskManager';
|
||||
import { BackgroundTaskManager } from '@/services/backgroundTaskManager';
|
||||
import { selectHealthDataByDate, setHealthData } from '@/store/healthSlice';
|
||||
import { fetchDailyMoodCheckins, selectLatestMoodRecordByDate } from '@/store/moodSlice';
|
||||
import { fetchDailyNutritionData, selectNutritionSummaryByDate } from '@/store/nutritionSlice';
|
||||
@@ -493,7 +493,7 @@ export default function ExploreScreen() {
|
||||
style={styles.debugButton}
|
||||
onPress={async () => {
|
||||
console.log('🔧 手动触发后台任务测试...');
|
||||
await backgroundTaskManager.triggerTaskForTesting();
|
||||
await BackgroundTaskManager.getInstance().triggerTaskForTesting();
|
||||
}}
|
||||
>
|
||||
<Text style={styles.debugButtonText}>🔧</Text>
|
||||
|
||||
Reference in New Issue
Block a user