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:
@@ -53,4 +53,26 @@ export const triggerErrorHaptic = () => {
|
||||
if (Platform.OS === 'ios') {
|
||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 通用震动反馈函数 (仅在 iOS 上生效)
|
||||
*/
|
||||
export const triggerHapticFeedback = (type: 'impactLight' | 'impactMedium' | 'impactHeavy' | 'success' | 'warning' | 'error') => {
|
||||
if (Platform.OS === 'ios') {
|
||||
switch (type) {
|
||||
case 'impactLight':
|
||||
return Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
||||
case 'impactMedium':
|
||||
return Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
|
||||
case 'impactHeavy':
|
||||
return Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy);
|
||||
case 'success':
|
||||
return Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
|
||||
case 'warning':
|
||||
return Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning);
|
||||
case 'error':
|
||||
return Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
|
||||
}
|
||||
}
|
||||
};
|
||||
113
utils/logger.ts
Normal file
113
utils/logger.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
|
||||
interface LogEntry {
|
||||
id: string;
|
||||
timestamp: number;
|
||||
level: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR';
|
||||
message: string;
|
||||
data?: any;
|
||||
}
|
||||
|
||||
class Logger {
|
||||
private static instance: Logger;
|
||||
private readonly maxLogs = 1000; // 最多保存1000条日志
|
||||
private readonly storageKey = '@app_logs';
|
||||
|
||||
static getInstance(): Logger {
|
||||
if (!Logger.instance) {
|
||||
Logger.instance = new Logger();
|
||||
}
|
||||
return Logger.instance;
|
||||
}
|
||||
|
||||
private async getLogs(): Promise<LogEntry[]> {
|
||||
try {
|
||||
const logsJson = await AsyncStorage.getItem(this.storageKey);
|
||||
return logsJson ? JSON.parse(logsJson) : [];
|
||||
} catch (error) {
|
||||
console.error('Failed to get logs from storage:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private async saveLogs(logs: LogEntry[]): Promise<void> {
|
||||
try {
|
||||
// 只保留最新的maxLogs条日志
|
||||
const trimmedLogs = logs.slice(-this.maxLogs);
|
||||
await AsyncStorage.setItem(this.storageKey, JSON.stringify(trimmedLogs));
|
||||
} catch (error) {
|
||||
console.error('Failed to save logs to storage:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private async addLog(level: LogEntry['level'], message: string, data?: any): Promise<void> {
|
||||
const logEntry: LogEntry = {
|
||||
id: Date.now().toString() + Math.random().toString(36).substr(2, 9),
|
||||
timestamp: Date.now(),
|
||||
level,
|
||||
message,
|
||||
data
|
||||
};
|
||||
|
||||
// 同时在控制台输出
|
||||
const logMethod = level === 'ERROR' ? console.error :
|
||||
level === 'WARN' ? console.warn :
|
||||
level === 'INFO' ? console.info : console.log;
|
||||
|
||||
logMethod(`[${level}] ${message}`, data || '');
|
||||
|
||||
try {
|
||||
const logs = await this.getLogs();
|
||||
logs.push(logEntry);
|
||||
await this.saveLogs(logs);
|
||||
} catch (error) {
|
||||
console.error('Failed to add log:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async debug(message: string, data?: any): Promise<void> {
|
||||
await this.addLog('DEBUG', message, data);
|
||||
}
|
||||
|
||||
async info(message: string, data?: any): Promise<void> {
|
||||
await this.addLog('INFO', message, data);
|
||||
}
|
||||
|
||||
async warn(message: string, data?: any): Promise<void> {
|
||||
await this.addLog('WARN', message, data);
|
||||
}
|
||||
|
||||
async error(message: string, data?: any): Promise<void> {
|
||||
await this.addLog('ERROR', message, data);
|
||||
}
|
||||
|
||||
async getAllLogs(): Promise<LogEntry[]> {
|
||||
return await this.getLogs();
|
||||
}
|
||||
|
||||
async clearLogs(): Promise<void> {
|
||||
try {
|
||||
await AsyncStorage.removeItem(this.storageKey);
|
||||
} catch (error) {
|
||||
console.error('Failed to clear logs:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async exportLogs(): Promise<string> {
|
||||
const logs = await this.getLogs();
|
||||
return JSON.stringify(logs, null, 2);
|
||||
}
|
||||
}
|
||||
|
||||
// 导出全局日志实例和便捷函数
|
||||
export const logger = Logger.getInstance();
|
||||
|
||||
// 便捷的全局日志函数
|
||||
export const log = {
|
||||
debug: (message: string, data?: any) => logger.debug(message, data),
|
||||
info: (message: string, data?: any) => logger.info(message, data),
|
||||
warn: (message: string, data?: any) => logger.warn(message, data),
|
||||
error: (message: string, data?: any) => logger.error(message, data),
|
||||
};
|
||||
|
||||
export type { LogEntry };
|
||||
Reference in New Issue
Block a user