feat(ios): 添加用药计划Widget小组件支持

- 创建medicineExtension小组件,支持iOS桌面显示用药计划
- 实现App Group数据共享机制,支持主应用与小组件数据同步
- 添加AppGroupUserDefaultsManager原生模块,提供跨应用数据访问能力
- 添加WidgetManager和WidgetCenterHelper,实现小组件刷新控制
- 在medications页面和Redux store中集成小组件数据同步逻辑
- 支持实时同步今日用药状态(待服用/已服用/已错过)到小组件
- 配置App Group entitlements (group.com.anonymous.digitalpilates)
- 更新Xcode项目配置,添加WidgetKit和SwiftUI框架支持
This commit is contained in:
richarjiang
2025-11-14 08:51:02 +08:00
parent d282abd146
commit b0e93eedae
25 changed files with 1423 additions and 4 deletions

View File

@@ -9,6 +9,7 @@ import type {
MedicationRecord,
MedicationStatus,
} from '@/types/medication';
import { convertMedicationDataToWidget, syncMedicationDataToWidget } from '@/utils/widgetDataSync';
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
import dayjs from 'dayjs';
import type { RootState } from './index';
@@ -345,6 +346,15 @@ const medicationsSlice = createSlice({
state.loading.records = false;
const { date, records } = action.payload;
state.medicationRecords[date] = records;
// 如果是今天的记录,同步到小组件
const today = dayjs().format('YYYY-MM-DD');
if (date === today) {
const medicationData = convertMedicationDataToWidget(records, state.medications, date);
syncMedicationDataToWidget(medicationData).catch(error => {
console.error('Failed to sync medication data to widget:', error);
});
}
})
.addCase(fetchMedicationRecords.rejected, (state, action) => {
state.loading.records = false;
@@ -361,6 +371,12 @@ const medicationsSlice = createSlice({
state.loading.records = false;
const { date, records } = action.payload;
state.medicationRecords[date] = records;
// 同步今天的记录到小组件
const medicationData = convertMedicationDataToWidget(records, state.medications, date);
syncMedicationDataToWidget(medicationData).catch(error => {
console.error('Failed to sync medication data to widget:', error);
});
})
.addCase(fetchTodayMedicationRecords.rejected, (state, action) => {
state.loading.records = false;
@@ -394,6 +410,15 @@ const medicationsSlice = createSlice({
state.medicationRecords[date].push(record);
}
});
// 同步今天的记录到小组件
const today = dayjs().format('YYYY-MM-DD');
if (state.medicationRecords[today]) {
const medicationData = convertMedicationDataToWidget(state.medicationRecords[today], state.medications, today);
syncMedicationDataToWidget(medicationData).catch(error => {
console.error('Failed to sync medication data to widget:', error);
});
}
})
.addCase(fetchMedicationRecordsByDateRange.rejected, (state, action) => {
state.loading.records = false;
@@ -571,6 +596,15 @@ const medicationsSlice = createSlice({
? (stats.taken / stats.totalScheduled) * 100
: 0;
}
// 如果是今天的记录,同步到小组件
const today = dayjs().format('YYYY-MM-DD');
if (date === today && records) {
const medicationData = convertMedicationDataToWidget(records, state.medications, date);
syncMedicationDataToWidget(medicationData).catch(error => {
console.error('Failed to sync medication data to widget:', error);
});
}
})
.addCase(takeMedicationAction.rejected, (state, action) => {
state.loading.takeMedication = false;
@@ -600,6 +634,15 @@ const medicationsSlice = createSlice({
if (stats) {
stats.upcoming = Math.max(0, stats.upcoming - 1);
}
// 如果是今天的记录,同步到小组件
const today = dayjs().format('YYYY-MM-DD');
if (date === today && records) {
const medicationData = convertMedicationDataToWidget(records, state.medications, date);
syncMedicationDataToWidget(medicationData).catch(error => {
console.error('Failed to sync medication data to widget:', error);
});
}
})
.addCase(skipMedicationAction.rejected, (state, action) => {
state.loading.takeMedication = false;