feat: 通知权限+推送
This commit is contained in:
parent
0307ed0a00
commit
5b23951643
@ -1,5 +1,6 @@
|
|||||||
import IP from '@/assets/icons/svg/ip.svg';
|
import IP from '@/assets/icons/svg/ip.svg';
|
||||||
import Lottie from '@/components/lottie/lottie';
|
import Lottie from '@/components/lottie/lottie';
|
||||||
|
import MessagePush from '@/components/message-push';
|
||||||
import { useRouter } from 'expo-router';
|
import { useRouter } from 'expo-router';
|
||||||
import * as SecureStore from 'expo-secure-store';
|
import * as SecureStore from 'expo-secure-store';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -35,7 +36,7 @@ export default function HomeScreen() {
|
|||||||
{"\n"}
|
{"\n"}
|
||||||
{t('auth.welcomeAwaken.back', { ns: 'login' })}
|
{t('auth.welcomeAwaken.back', { ns: 'login' })}
|
||||||
</Text>
|
</Text>
|
||||||
|
<MessagePush />
|
||||||
{/* 唤醒按钮 */}
|
{/* 唤醒按钮 */}
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
className="bg-white rounded-full px-10 py-4 shadow-[0_2px_4px_rgba(0,0,0,0.1)] w-full items-center"
|
className="bg-white rounded-full px-10 py-4 shadow-[0_2px_4px_rgba(0,0,0,0.1)] w-full items-center"
|
||||||
|
|||||||
76
components/message-push.tsx
Normal file
76
components/message-push.tsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import * as Notifications from 'expo-notifications';
|
||||||
|
import { useRouter } from 'expo-router';
|
||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import { Button, Text, View } from 'react-native';
|
||||||
|
|
||||||
|
Notifications.setNotificationHandler({
|
||||||
|
handleNotification: async () => ({
|
||||||
|
shouldShowAlert: true,
|
||||||
|
shouldPlaySound: true,
|
||||||
|
shouldSetBadge: false,
|
||||||
|
shouldShowBanner: true,
|
||||||
|
shouldShowList: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function MessagePush() {
|
||||||
|
const router = useRouter();
|
||||||
|
const notificationListener = useRef<Notifications.Subscription>(null);
|
||||||
|
const responseListener = useRef<Notifications.Subscription>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// 监听通知点击事件
|
||||||
|
responseListener.current = Notifications.addNotificationResponseReceivedListener(response => {
|
||||||
|
const data = response.notification.request.content.data;
|
||||||
|
console.log('通知被点击,数据:', data);
|
||||||
|
|
||||||
|
// 根据通知数据跳转到指定页面
|
||||||
|
if (data.screen === 'ask') {
|
||||||
|
router.push('/ask');
|
||||||
|
} else if (data.screen === 'owner') {
|
||||||
|
router.push('/owner');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 清理监听器
|
||||||
|
return () => {
|
||||||
|
if (notificationListener.current) {
|
||||||
|
Notifications.removeNotificationSubscription(notificationListener.current);
|
||||||
|
}
|
||||||
|
if (responseListener.current) {
|
||||||
|
Notifications.removeNotificationSubscription(responseListener.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
const sendNotification = async () => {
|
||||||
|
// 请求通知权限
|
||||||
|
const { status } = await Notifications.requestPermissionsAsync();
|
||||||
|
if (status !== 'granted') {
|
||||||
|
alert('请先允许通知权限');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调度本地通知
|
||||||
|
await Notifications.scheduleNotificationAsync({
|
||||||
|
content: {
|
||||||
|
title: '你有一条新消息 🎉',
|
||||||
|
body: '点击查看详情内容',
|
||||||
|
data: { screen: 'ask' },
|
||||||
|
priority: 'high', // 关键:设置 high 或 max
|
||||||
|
},
|
||||||
|
trigger: {
|
||||||
|
seconds: 2, // 延迟2秒显示
|
||||||
|
type: Notifications.SchedulableTriggerInputTypes.TIME_INTERVAL // 添加 type 字段
|
||||||
|
}, // 延迟2秒显示
|
||||||
|
});
|
||||||
|
|
||||||
|
alert('通知将在2秒后显示');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20 }}>
|
||||||
|
<Text>点击按钮发送本地通知</Text>
|
||||||
|
<Button title="发送通知" onPress={sendNotification} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -13,7 +13,7 @@ import LcensesModal from './qualification/lcenses';
|
|||||||
import PrivacyModal from './qualification/privacy';
|
import PrivacyModal from './qualification/privacy';
|
||||||
import CustomSwitch from './switch';
|
import CustomSwitch from './switch';
|
||||||
import UserInfo from './userInfo';
|
import UserInfo from './userInfo';
|
||||||
import { getLocationPermission, getPermissions, requestLocationPermission, requestMediaLibraryPermission, reverseGeocode } from './utils';
|
import { checkNotificationPermission, getLocationPermission, getPermissions, requestLocationPermission, requestMediaLibraryPermission, requestNotificationPermission, reverseGeocode } from './utils';
|
||||||
|
|
||||||
const SettingModal = (props: { modalVisible: boolean, setModalVisible: (visible: boolean) => void, userInfo: User }) => {
|
const SettingModal = (props: { modalVisible: boolean, setModalVisible: (visible: boolean) => void, userInfo: User }) => {
|
||||||
const { modalVisible, setModalVisible, userInfo } = props;
|
const { modalVisible, setModalVisible, userInfo } = props;
|
||||||
@ -32,7 +32,17 @@ const SettingModal = (props: { modalVisible: boolean, setModalVisible: (visible:
|
|||||||
};
|
};
|
||||||
// 通知消息权限开关
|
// 通知消息权限开关
|
||||||
const [notificationsEnabled, setNotificationsEnabled] = useState(false);
|
const [notificationsEnabled, setNotificationsEnabled] = useState(false);
|
||||||
const toggleNotifications = () => setNotificationsEnabled(previous => !previous);
|
const toggleNotifications = () => {
|
||||||
|
if (notificationsEnabled) {
|
||||||
|
// 引导去设置关闭权限
|
||||||
|
openAppSettings()
|
||||||
|
} else {
|
||||||
|
console.log('请求通知权限');
|
||||||
|
requestNotificationPermission().then((res) => {
|
||||||
|
setNotificationsEnabled(res as boolean);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 相册权限
|
// 相册权限
|
||||||
const [albumEnabled, setAlbumEnabled] = useState(false);
|
const [albumEnabled, setAlbumEnabled] = useState(false);
|
||||||
@ -126,7 +136,6 @@ const SettingModal = (props: { modalVisible: boolean, setModalVisible: (visible:
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 退出登录
|
// 退出登录
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
fetchApi("/iam/logout", {
|
fetchApi("/iam/logout", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@ -154,6 +163,11 @@ const SettingModal = (props: { modalVisible: boolean, setModalVisible: (visible:
|
|||||||
getPermissions().then((res) => {
|
getPermissions().then((res) => {
|
||||||
setAlbumEnabled(res);
|
setAlbumEnabled(res);
|
||||||
})
|
})
|
||||||
|
// 通知权限
|
||||||
|
checkNotificationPermission().then((res) => {
|
||||||
|
console.log('通知权限:', res);
|
||||||
|
setNotificationsEnabled(res);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}, [modalVisible])
|
}, [modalVisible])
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,21 @@
|
|||||||
// 地理位置逆编码
|
// 地理位置逆编码
|
||||||
import * as ImagePicker from 'expo-image-picker';
|
import * as ImagePicker from 'expo-image-picker';
|
||||||
import * as Location from 'expo-location';
|
import * as Location from 'expo-location';
|
||||||
|
import * as Notifications from 'expo-notifications';
|
||||||
import * as SecureStore from 'expo-secure-store';
|
import * as SecureStore from 'expo-secure-store';
|
||||||
import { Alert, Linking, Platform } from 'react-native';
|
import { Alert, Linking, Platform } from 'react-native';
|
||||||
|
|
||||||
|
// 配置通知处理器
|
||||||
|
Notifications.setNotificationHandler({
|
||||||
|
handleNotification: async () => ({
|
||||||
|
shouldShowAlert: true,
|
||||||
|
shouldPlaySound: true,
|
||||||
|
shouldSetBadge: false,
|
||||||
|
shouldShowBanner: true,
|
||||||
|
shouldShowList: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
export const reverseGeocode = async (latitude: number, longitude: number) => {
|
export const reverseGeocode = async (latitude: number, longitude: number) => {
|
||||||
try {
|
try {
|
||||||
const addressResults = await Location.reverseGeocodeAsync({ latitude, longitude });
|
const addressResults = await Location.reverseGeocodeAsync({ latitude, longitude });
|
||||||
@ -192,4 +205,98 @@ export const requestMediaLibraryPermission = async (showAlert: boolean = true):
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 检查通知权限
|
||||||
|
export const checkNotificationPermission = async () => {
|
||||||
|
const { status } = await Notifications.getPermissionsAsync();
|
||||||
|
console.log('当前通知权限状态:', status);
|
||||||
|
|
||||||
|
return status === 'granted';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 请求通知权限
|
||||||
|
export const requestNotificationPermission = async () => {
|
||||||
|
try {
|
||||||
|
// 1. 先检查当前权限状态
|
||||||
|
const { status, canAskAgain } = await Notifications.getPermissionsAsync();
|
||||||
|
console.log('当前通知权限状态:', { status, canAskAgain });
|
||||||
|
|
||||||
|
// 2. 如果已经有权限,直接返回
|
||||||
|
if (status === 'granted') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 如果用户之前选择了"拒绝且不再询问"
|
||||||
|
if (status === 'denied' && !canAskAgain) {
|
||||||
|
// 显示提示,引导用户去设置
|
||||||
|
const openSettings = await new Promise(resolve => {
|
||||||
|
Alert.alert(
|
||||||
|
'需要通知权限',
|
||||||
|
'您之前拒绝了通知权限。要使用此功能,请在设置中启用通知权限。',
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: '取消',
|
||||||
|
style: 'cancel',
|
||||||
|
onPress: () => resolve(false)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '去设置',
|
||||||
|
onPress: () => resolve(true)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (openSettings) {
|
||||||
|
// 打开应用设置
|
||||||
|
await Linking.openSettings();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 如果是第一次请求或可以再次询问,则请求权限
|
||||||
|
console.log('请求通知权限...');
|
||||||
|
const { status: newStatus } = await Notifications.requestPermissionsAsync();
|
||||||
|
console.log('新通知权限状态:', newStatus);
|
||||||
|
|
||||||
|
if (newStatus !== 'granted') {
|
||||||
|
Alert.alert('需要通知权限', '请允许通知以使用此功能');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('请求通知权限时出错:', error);
|
||||||
|
Alert.alert('错误', '请求通知权限时出错');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 发送本地通知的辅助函数
|
||||||
|
export const sendLocalNotification = async (title: string, body: string, data: Record<string, any> = {}) => {
|
||||||
|
try {
|
||||||
|
const hasPermission = await checkNotificationPermission();
|
||||||
|
if (!hasPermission) {
|
||||||
|
const granted = await requestNotificationPermission();
|
||||||
|
if (!granted) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Notifications.scheduleNotificationAsync({
|
||||||
|
content: {
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
data,
|
||||||
|
priority: 'high',
|
||||||
|
},
|
||||||
|
trigger: null, // 立即触发
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('发送通知时出错:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user