feat(medications): 重构药品通知系统并添加独立设置页面
- 创建药品通知服务模块,统一管理药品提醒通知的调度和取消 - 新增独立的通知设置页面,支持总开关和药品提醒开关分离控制 - 重构药品详情页面,移除频率编辑功能到独立页面 - 优化药品添加流程,支持拍照和相册选择图片 - 改进通知权限检查和错误处理机制 - 更新用户偏好设置,添加药品提醒开关配置
This commit is contained in:
@@ -7,6 +7,7 @@ import { useAppDispatch } from '@/hooks/redux';
|
||||
import { useAuthGuard } from '@/hooks/useAuthGuard';
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import { useCosUpload } from '@/hooks/useCosUpload';
|
||||
import { medicationNotificationService } from '@/services/medicationNotifications';
|
||||
import { createMedicationAction, fetchMedicationRecords, fetchMedications } from '@/store/medicationsSlice';
|
||||
import type { MedicationForm, RepeatPattern } from '@/types/medication';
|
||||
import { Ionicons, MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
@@ -288,6 +289,16 @@ export default function AddMedicationScreen() {
|
||||
const today = dayjs().format('YYYY-MM-DD');
|
||||
await dispatch(fetchMedicationRecords({ date: today }));
|
||||
|
||||
// 重新安排药品通知
|
||||
try {
|
||||
// 获取最新的药品列表
|
||||
const medications = await dispatch(fetchMedications({ isActive: true })).unwrap();
|
||||
await medicationNotificationService.rescheduleAllMedicationNotifications(medications);
|
||||
} catch (error) {
|
||||
console.error('[MEDICATION] 安排药品通知失败:', error);
|
||||
// 不影响添加药品的成功流程,只记录错误
|
||||
}
|
||||
|
||||
// 成功提示
|
||||
Alert.alert(
|
||||
'添加成功',
|
||||
@@ -357,42 +368,99 @@ export default function AddMedicationScreen() {
|
||||
}
|
||||
}, [dictationActive, dictationLoading, isDictationSupported]);
|
||||
|
||||
const handleTakePhoto = useCallback(async () => {
|
||||
try {
|
||||
const permission = await ImagePicker.requestCameraPermissionsAsync();
|
||||
if (permission.status !== 'granted') {
|
||||
Alert.alert('权限不足', '需要相机权限以拍摄药品照片');
|
||||
return;
|
||||
}
|
||||
// 处理图片选择(拍照或相册)
|
||||
const handleSelectPhoto = useCallback(() => {
|
||||
Alert.alert(
|
||||
'选择图片',
|
||||
'请选择图片来源',
|
||||
[
|
||||
{
|
||||
text: '拍照',
|
||||
onPress: async () => {
|
||||
try {
|
||||
const permission = await ImagePicker.requestCameraPermissionsAsync();
|
||||
if (permission.status !== 'granted') {
|
||||
Alert.alert('权限不足', '需要相机权限以拍摄药品照片');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await ImagePicker.launchCameraAsync({
|
||||
allowsEditing: true,
|
||||
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
||||
quality: 0.9,
|
||||
});
|
||||
const result = await ImagePicker.launchCameraAsync({
|
||||
allowsEditing: true,
|
||||
quality: 0.9,
|
||||
aspect: [9,16]
|
||||
});
|
||||
|
||||
if (result.canceled || !result.assets?.length) {
|
||||
return;
|
||||
}
|
||||
if (result.canceled || !result.assets?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const asset = result.assets[0];
|
||||
setPhotoPreview(asset.uri);
|
||||
setPhotoUrl(null);
|
||||
const asset = result.assets[0];
|
||||
setPhotoPreview(asset.uri);
|
||||
setPhotoUrl(null);
|
||||
|
||||
try {
|
||||
const { url } = await upload(
|
||||
{ uri: asset.uri, name: asset.fileName ?? `medication-${Date.now()}.jpg`, type: asset.mimeType ?? 'image/jpeg' },
|
||||
{ prefix: 'images/medications' }
|
||||
);
|
||||
setPhotoUrl(url);
|
||||
} catch (uploadError) {
|
||||
console.error('[MEDICATION] 图片上传失败', uploadError);
|
||||
Alert.alert('上传失败', '图片上传失败,请稍后重试');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[MEDICATION] 拍照失败', error);
|
||||
Alert.alert('拍照失败', '无法打开相机,请稍后再试');
|
||||
}
|
||||
try {
|
||||
const { url } = await upload(
|
||||
{ uri: asset.uri, name: asset.fileName ?? `medication-${Date.now()}.jpg`, type: asset.mimeType ?? 'image/jpeg' },
|
||||
{ prefix: 'images/medications' }
|
||||
);
|
||||
setPhotoUrl(url);
|
||||
} catch (uploadError) {
|
||||
console.error('[MEDICATION] 图片上传失败', uploadError);
|
||||
Alert.alert('上传失败', '图片上传失败,请稍后重试');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[MEDICATION] 拍照失败', error);
|
||||
Alert.alert('拍照失败', '无法打开相机,请稍后再试');
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
text: '从相册选择',
|
||||
onPress: async () => {
|
||||
try {
|
||||
const permission = await ImagePicker.requestMediaLibraryPermissionsAsync();
|
||||
if (permission.status !== 'granted') {
|
||||
Alert.alert('权限不足', '需要相册权限以选择药品照片');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await ImagePicker.launchImageLibraryAsync({
|
||||
allowsEditing: true,
|
||||
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
||||
quality: 0.9,
|
||||
});
|
||||
|
||||
if (result.canceled || !result.assets?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const asset = result.assets[0];
|
||||
setPhotoPreview(asset.uri);
|
||||
setPhotoUrl(null);
|
||||
|
||||
try {
|
||||
const { url } = await upload(
|
||||
{ uri: asset.uri, name: asset.fileName ?? `medication-${Date.now()}.jpg`, type: asset.mimeType ?? 'image/jpeg' },
|
||||
{ prefix: 'images/medications' }
|
||||
);
|
||||
setPhotoUrl(url);
|
||||
} catch (uploadError) {
|
||||
console.error('[MEDICATION] 图片上传失败', uploadError);
|
||||
Alert.alert('上传失败', '图片上传失败,请稍后重试');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[MEDICATION] 从相册选择失败', error);
|
||||
Alert.alert('选择失败', '无法打开相册,请稍后再试');
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
text: '取消',
|
||||
style: 'cancel',
|
||||
},
|
||||
],
|
||||
{ cancelable: true }
|
||||
);
|
||||
}, [upload]);
|
||||
|
||||
const handleRemovePhoto = useCallback(() => {
|
||||
@@ -539,7 +607,7 @@ export default function AddMedicationScreen() {
|
||||
backgroundColor: colors.surface,
|
||||
},
|
||||
]}
|
||||
onPress={handleTakePhoto}
|
||||
onPress={handleSelectPhoto}
|
||||
disabled={uploading}
|
||||
>
|
||||
{photoPreview ? (
|
||||
@@ -548,7 +616,7 @@ export default function AddMedicationScreen() {
|
||||
<View style={styles.photoOverlay}>
|
||||
<Ionicons name="camera" size={18} color="#fff" />
|
||||
<ThemedText style={styles.photoOverlayText}>
|
||||
{uploading ? '上传中…' : '重新拍摄'}
|
||||
{uploading ? '上传中…' : '重新选择'}
|
||||
</ThemedText>
|
||||
</View>
|
||||
<Pressable style={styles.photoRemoveBtn} onPress={handleRemovePhoto} hitSlop={12}>
|
||||
@@ -560,8 +628,8 @@ export default function AddMedicationScreen() {
|
||||
<View style={[styles.photoIconBadge, { backgroundColor: `${colors.primary}12` }]}>
|
||||
<Ionicons name="camera" size={22} color={colors.primary} />
|
||||
</View>
|
||||
<ThemedText style={[styles.photoTitle, { color: colors.text }]}>拍照上传药品图片</ThemedText>
|
||||
<ThemedText style={[styles.photoSubtitle, { color: colors.textMuted }]}>辅助识别药品包装,更易区分</ThemedText>
|
||||
<ThemedText style={[styles.photoTitle, { color: colors.text }]}>上传药品图片</ThemedText>
|
||||
<ThemedText style={[styles.photoSubtitle, { color: colors.textMuted }]}>拍照或从相册选择,辅助识别药品包装</ThemedText>
|
||||
</View>
|
||||
)}
|
||||
{uploading && (
|
||||
|
||||
Reference in New Issue
Block a user