284 lines
9.3 KiB
TypeScript
284 lines
9.3 KiB
TypeScript
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);
|
||
}
|
||
}; |