Files
digital-pilates/utils/widgetDataSync.ts
2025-09-09 14:26:16 +08:00

284 lines
9.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import AsyncStorage from '@react-native-async-storage/async-storage';
import { NativeModules } from 'react-native';
// Widget数据同步服务
// 用于在主App和iOS Widget之间同步饮水数据
// App Group标识符
const APP_GROUP_ID = 'group.com.anonymous.digitalpilates';
// Widget数据存储键
const WIDGET_DATA_KEYS = {
CURRENT_WATER_INTAKE: 'widget_current_water_intake',
DAILY_WATER_GOAL: 'widget_daily_water_goal',
QUICK_ADD_AMOUNT: 'widget_quick_add_amount',
LAST_SYNC_TIME: 'widget_last_sync_time',
PENDING_WATER_RECORDS: 'widget_pending_water_records',
} as const;
export interface WidgetWaterData {
currentIntake: number;
dailyGoal: number;
quickAddAmount: number;
lastSyncTime: string;
}
// 默认数据
const DEFAULT_WIDGET_DATA: WidgetWaterData = {
currentIntake: 0,
dailyGoal: 2000,
quickAddAmount: 150,
lastSyncTime: new Date().toISOString(),
};
/**
* 创建Native Module来访问App Group UserDefaults
* 这需要在iOS原生代码中实现
*/
interface AppGroupUserDefaults {
setString: (groupId: string, key: string, value: string) => Promise<void>;
getString: (groupId: string, key: string) => Promise<string | null>;
setNumber: (groupId: string, key: string, value: number) => Promise<void>;
getNumber: (groupId: string, key: string) => Promise<number>;
removeKey: (groupId: string, key: string) => Promise<void>;
setArray: (groupId: string, key: string, value: any[]) => Promise<void>;
getArray: (groupId: string, key: string) => Promise<any[] | null>;
}
// Widget待同步水记录接口
export interface PendingWaterRecord {
amount: number;
recordedAt: string;
source: string;
widgetAdded: boolean;
}
// 尝试获取原生模块如果不存在则使用fallback
const AppGroupDefaults: AppGroupUserDefaults | null = NativeModules.AppGroupUserDefaults || null;
/**
* 将饮水数据同步到Widget
*/
export const syncWaterDataToWidget = async (data: Partial<WidgetWaterData>): Promise<void> => {
try {
if (!AppGroupDefaults) {
console.warn('AppGroupUserDefaults native module not available, falling back to AsyncStorage');
// Fallback到AsyncStorage (仅主App可用)
const currentData = await getWidgetDataFromAsyncStorage();
const updatedData = { ...currentData, ...data, lastSyncTime: new Date().toISOString() };
await saveWidgetDataToAsyncStorage(updatedData);
return;
}
// 使用App Group UserDefaults
if (data.currentIntake !== undefined) {
await AppGroupDefaults.setNumber(APP_GROUP_ID, WIDGET_DATA_KEYS.CURRENT_WATER_INTAKE, data.currentIntake);
}
if (data.dailyGoal !== undefined) {
await AppGroupDefaults.setNumber(APP_GROUP_ID, WIDGET_DATA_KEYS.DAILY_WATER_GOAL, data.dailyGoal);
}
if (data.quickAddAmount !== undefined) {
await AppGroupDefaults.setNumber(APP_GROUP_ID, WIDGET_DATA_KEYS.QUICK_ADD_AMOUNT, data.quickAddAmount);
}
// 更新同步时间
await AppGroupDefaults.setString(APP_GROUP_ID, WIDGET_DATA_KEYS.LAST_SYNC_TIME, new Date().toISOString());
console.log('Widget data synced successfully:', data);
} catch (error) {
console.error('Failed to sync data to widget:', error);
throw error;
}
};
/**
* 从Widget获取饮水数据
*/
export const getWidgetWaterData = async (): Promise<WidgetWaterData> => {
try {
if (!AppGroupDefaults) {
console.warn('AppGroupUserDefaults native module not available, falling back to AsyncStorage');
return await getWidgetDataFromAsyncStorage();
}
// 从App Group UserDefaults读取数据
const currentIntake = await AppGroupDefaults.getNumber(APP_GROUP_ID, WIDGET_DATA_KEYS.CURRENT_WATER_INTAKE);
const dailyGoal = await AppGroupDefaults.getNumber(APP_GROUP_ID, WIDGET_DATA_KEYS.DAILY_WATER_GOAL);
const quickAddAmount = await AppGroupDefaults.getNumber(APP_GROUP_ID, WIDGET_DATA_KEYS.QUICK_ADD_AMOUNT);
const lastSyncTime = await AppGroupDefaults.getString(APP_GROUP_ID, WIDGET_DATA_KEYS.LAST_SYNC_TIME);
return {
currentIntake: currentIntake || DEFAULT_WIDGET_DATA.currentIntake,
dailyGoal: dailyGoal || DEFAULT_WIDGET_DATA.dailyGoal,
quickAddAmount: quickAddAmount || DEFAULT_WIDGET_DATA.quickAddAmount,
lastSyncTime: lastSyncTime || DEFAULT_WIDGET_DATA.lastSyncTime,
};
} catch (error) {
console.error('Failed to get widget data:', error);
return DEFAULT_WIDGET_DATA;
}
};
/**
* 清除Widget数据
*/
export const clearWidgetData = async (): Promise<void> => {
try {
if (!AppGroupDefaults) {
await AsyncStorage.multiRemove(Object.values(WIDGET_DATA_KEYS));
return;
}
// 清除App Group UserDefaults中的数据
await Promise.all([
AppGroupDefaults.removeKey(APP_GROUP_ID, WIDGET_DATA_KEYS.CURRENT_WATER_INTAKE),
AppGroupDefaults.removeKey(APP_GROUP_ID, WIDGET_DATA_KEYS.DAILY_WATER_GOAL),
AppGroupDefaults.removeKey(APP_GROUP_ID, WIDGET_DATA_KEYS.QUICK_ADD_AMOUNT),
AppGroupDefaults.removeKey(APP_GROUP_ID, WIDGET_DATA_KEYS.LAST_SYNC_TIME),
]);
console.log('Widget data cleared successfully');
} catch (error) {
console.error('Failed to clear widget data:', error);
throw error;
}
};
/**
* Fallback: 使用AsyncStorage存储Widget数据
*/
const saveWidgetDataToAsyncStorage = async (data: WidgetWaterData): Promise<void> => {
const dataToStore = [
[WIDGET_DATA_KEYS.CURRENT_WATER_INTAKE, data.currentIntake.toString()],
[WIDGET_DATA_KEYS.DAILY_WATER_GOAL, data.dailyGoal.toString()],
[WIDGET_DATA_KEYS.QUICK_ADD_AMOUNT, data.quickAddAmount.toString()],
[WIDGET_DATA_KEYS.LAST_SYNC_TIME, data.lastSyncTime],
];
await AsyncStorage.multiSet(dataToStore);
};
/**
* Fallback: 从AsyncStorage读取Widget数据
*/
const getWidgetDataFromAsyncStorage = async (): Promise<WidgetWaterData> => {
const keys = Object.values(WIDGET_DATA_KEYS);
const values = await AsyncStorage.multiGet(keys);
const data: any = {};
values.forEach(([key, value]) => {
if (value !== null) {
if (key === WIDGET_DATA_KEYS.LAST_SYNC_TIME) {
data[key] = value;
} else {
data[key] = parseInt(value, 10);
}
}
});
return {
currentIntake: data[WIDGET_DATA_KEYS.CURRENT_WATER_INTAKE] || DEFAULT_WIDGET_DATA.currentIntake,
dailyGoal: data[WIDGET_DATA_KEYS.DAILY_WATER_GOAL] || DEFAULT_WIDGET_DATA.dailyGoal,
quickAddAmount: data[WIDGET_DATA_KEYS.QUICK_ADD_AMOUNT] || DEFAULT_WIDGET_DATA.quickAddAmount,
lastSyncTime: data[WIDGET_DATA_KEYS.LAST_SYNC_TIME] || DEFAULT_WIDGET_DATA.lastSyncTime,
};
};
/**
* 触发Widget刷新
* 通知iOS系统更新Widget
*/
export const refreshWidget = async (): Promise<void> => {
try {
if (NativeModules.WidgetManager?.reloadTimelines) {
await NativeModules.WidgetManager.reloadTimelines();
console.log('Widget refresh triggered');
} else {
console.warn('WidgetManager native module not available');
}
} catch (error) {
console.error('Failed to refresh widget:', error);
}
};
/**
* 获取待同步的水记录
*/
export const getPendingWaterRecords = async (): Promise<PendingWaterRecord[]> => {
try {
if (!AppGroupDefaults) {
// Fallback: 从 AsyncStorage 读取
const pendingRecordsJson = await AsyncStorage.getItem(WIDGET_DATA_KEYS.PENDING_WATER_RECORDS);
return pendingRecordsJson ? JSON.parse(pendingRecordsJson) : [];
}
// 从 App Group UserDefaults 读取
const pendingRecords = await AppGroupDefaults.getArray(APP_GROUP_ID, WIDGET_DATA_KEYS.PENDING_WATER_RECORDS);
return pendingRecords || [];
} catch (error) {
console.error('Failed to get pending water records:', error);
return [];
}
};
/**
* 清除待同步的水记录
*/
export const clearPendingWaterRecords = async (): Promise<void> => {
try {
if (!AppGroupDefaults) {
// Fallback: 从 AsyncStorage 清除
await AsyncStorage.removeItem(WIDGET_DATA_KEYS.PENDING_WATER_RECORDS);
return;
}
// 从 App Group UserDefaults 清除
await AppGroupDefaults.removeKey(APP_GROUP_ID, WIDGET_DATA_KEYS.PENDING_WATER_RECORDS);
console.log('Pending water records cleared successfully');
} catch (error) {
console.error('Failed to clear pending water records:', error);
}
};
/**
* 从Widget同步待处理的数据更改到主App
* 检查Widget中的数据更改并同步到主App的数据存储
*/
export const syncPendingWidgetChanges = async (): Promise<{
hasPendingChanges: boolean;
pendingRecords?: PendingWaterRecord[];
lastSyncTime?: string;
}> => {
try {
// 获取待同步的水记录
const pendingRecords = await getPendingWaterRecords();
if (pendingRecords.length > 0) {
console.log(`发现 ${pendingRecords.length} 条待同步的水记录`);
return {
hasPendingChanges: true,
pendingRecords,
lastSyncTime: new Date().toISOString(),
};
}
return { hasPendingChanges: false };
} catch (error) {
console.error('Failed to sync pending widget changes:', error);
return { hasPendingChanges: false };
}
};
/**
* 标记Widget数据已被主App同步
*/
export const markWidgetDataSynced = async (): Promise<void> => {
try {
const currentTime = new Date().toISOString();
await AsyncStorage.setItem('last_app_widget_sync', currentTime);
} catch (error) {
console.error('Failed to mark widget data as synced:', error);
}
};