feat: 集成推送通知功能及相关组件
- 在项目中引入expo-notifications库,支持本地推送通知功能 - 实现通知权限管理,用户可选择开启或关闭通知 - 新增通知发送、定时通知和重复通知功能 - 更新个人页面,集成通知开关和权限请求逻辑 - 编写推送通知功能实现文档,详细描述功能和使用方法 - 优化心情日历页面,确保数据实时刷新
This commit is contained in:
@@ -6,7 +6,7 @@ import { Colors } from '@/constants/Colors';
|
||||
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import { clearErrors, createGoal } from '@/store/goalsSlice';
|
||||
import { clearErrors as clearTaskErrors, completeTask, fetchTasks, loadMoreTasks, skipTask } from '@/store/tasksSlice';
|
||||
import { clearErrors as clearTaskErrors, fetchTasks, loadMoreTasks } from '@/store/tasksSlice';
|
||||
import { CreateGoalRequest, TaskListItem } from '@/types/goals';
|
||||
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
@@ -122,40 +122,7 @@ export default function GoalsScreen() {
|
||||
}
|
||||
};
|
||||
|
||||
// 任务点击处理
|
||||
const handleTaskPress = (task: TaskListItem) => {
|
||||
console.log('Task pressed:', task.title);
|
||||
// 这里可以导航到任务详情页面
|
||||
};
|
||||
|
||||
// 完成任务处理
|
||||
const handleCompleteTask = async (task: TaskListItem) => {
|
||||
try {
|
||||
await dispatch(completeTask({
|
||||
taskId: task.id,
|
||||
completionData: {
|
||||
count: 1,
|
||||
notes: '通过任务卡片完成'
|
||||
}
|
||||
})).unwrap();
|
||||
} catch (error) {
|
||||
Alert.alert('错误', '完成任务失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 跳过任务处理
|
||||
const handleSkipTask = async (task: TaskListItem) => {
|
||||
try {
|
||||
await dispatch(skipTask({
|
||||
taskId: task.id,
|
||||
skipData: {
|
||||
reason: '用户主动跳过'
|
||||
}
|
||||
})).unwrap();
|
||||
} catch (error) {
|
||||
Alert.alert('错误', '跳过任务失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 导航到目标管理页面
|
||||
const handleNavigateToGoals = () => {
|
||||
@@ -190,9 +157,6 @@ export default function GoalsScreen() {
|
||||
const renderTaskItem = ({ item }: { item: TaskListItem }) => (
|
||||
<TaskCard
|
||||
task={item}
|
||||
onPress={handleTaskPress}
|
||||
onComplete={handleCompleteTask}
|
||||
onSkip={handleSkipTask}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -5,12 +5,13 @@ import { getTabBarBottomPadding } from '@/constants/TabBar';
|
||||
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||||
import { useAuthGuard } from '@/hooks/useAuthGuard';
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import { useNotifications } from '@/hooks/useNotifications';
|
||||
import { DEFAULT_MEMBER_NAME, fetchActivityHistory, fetchMyProfile } from '@/store/userSlice';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { Image, Linking, SafeAreaView, ScrollView, StatusBar, StyleSheet, Switch, Text, TouchableOpacity, View } from 'react-native';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Alert, Image, Linking, SafeAreaView, ScrollView, StatusBar, StyleSheet, Switch, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
const DEFAULT_AVATAR_URL = 'https://plates-1251306435.cos.ap-guangzhou.myqcloud.com/images/avatar/avatarGirl01.jpeg';
|
||||
@@ -21,7 +22,16 @@ export default function PersonalScreen() {
|
||||
const insets = useSafeAreaInsets();
|
||||
const tabBarHeight = useBottomTabBarHeight();
|
||||
const colorScheme = useColorScheme();
|
||||
const [notificationEnabled, setNotificationEnabled] = useState(true);
|
||||
|
||||
// 推送通知相关
|
||||
const {
|
||||
isInitialized,
|
||||
permissionStatus,
|
||||
requestPermission,
|
||||
sendNotification,
|
||||
} = useNotifications();
|
||||
|
||||
const [notificationEnabled, setNotificationEnabled] = useState(false);
|
||||
|
||||
// 计算底部间距
|
||||
const bottomPadding = useMemo(() => {
|
||||
@@ -64,6 +74,41 @@ export default function PersonalScreen() {
|
||||
// 显示名称
|
||||
const displayName = (userProfile.name?.trim()) ? userProfile.name : DEFAULT_MEMBER_NAME;
|
||||
|
||||
// 监听通知权限状态变化
|
||||
useEffect(() => {
|
||||
if (permissionStatus === 'granted') {
|
||||
setNotificationEnabled(true);
|
||||
} else {
|
||||
setNotificationEnabled(false);
|
||||
}
|
||||
}, [permissionStatus]);
|
||||
|
||||
// 处理通知开关变化
|
||||
const handleNotificationToggle = async (value: boolean) => {
|
||||
if (value) {
|
||||
try {
|
||||
const status = await requestPermission();
|
||||
if (status === 'granted') {
|
||||
setNotificationEnabled(true);
|
||||
// 发送测试通知
|
||||
await sendNotification({
|
||||
title: '通知已开启',
|
||||
body: '您将收到运动提醒和重要通知',
|
||||
sound: true,
|
||||
priority: 'normal',
|
||||
});
|
||||
} else {
|
||||
Alert.alert('权限被拒绝', '请在系统设置中开启通知权限');
|
||||
}
|
||||
} catch (error) {
|
||||
Alert.alert('错误', '请求通知权限失败');
|
||||
}
|
||||
} else {
|
||||
setNotificationEnabled(false);
|
||||
Alert.alert('通知已关闭', '您将不会收到推送通知');
|
||||
}
|
||||
};
|
||||
|
||||
// 用户信息头部
|
||||
const UserHeader = () => (
|
||||
<View style={styles.sectionContainer}>
|
||||
@@ -137,14 +182,8 @@ export default function PersonalScreen() {
|
||||
</View>
|
||||
{item.type === 'switch' ? (
|
||||
<Switch
|
||||
value={isLoggedIn ? notificationEnabled : false}
|
||||
onValueChange={(value) => {
|
||||
if (!isLoggedIn) {
|
||||
pushIfAuthedElseLogin('/profile/notification-settings');
|
||||
return;
|
||||
}
|
||||
setNotificationEnabled(value);
|
||||
}}
|
||||
value={item.switchValue || false}
|
||||
onValueChange={item.onSwitchChange || (() => {})}
|
||||
trackColor={{ false: '#E5E5E5', true: '#9370DB' }}
|
||||
thumbColor="#FFFFFF"
|
||||
style={styles.switch}
|
||||
@@ -177,6 +216,8 @@ export default function PersonalScreen() {
|
||||
icon: 'notifications-outline' as const,
|
||||
title: '消息推送',
|
||||
type: 'switch' as const,
|
||||
switchValue: notificationEnabled,
|
||||
onSwitchChange: handleNotificationToggle,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user