1import { Subscription } from '@unimodules/core'; 2import * as Notifications from 'expo-notifications'; 3import React from 'react'; 4import { Alert, Platform, ScrollView } from 'react-native'; 5 6import registerForPushNotificationsAsync from '../api/registerForPushNotificationsAsync'; 7import HeadingText from '../components/HeadingText'; 8import ListButton from '../components/ListButton'; 9import MonoText from '../components/MonoText'; 10 11export default class NotificationScreen extends React.Component< 12 // See: https://github.com/expo/expo/pull/10229#discussion_r490961694 13 // eslint-disable-next-line @typescript-eslint/ban-types 14 {}, 15 { 16 lastNotifications?: Notifications.Notification; 17 } 18> { 19 static navigationOptions = { 20 title: 'Notifications', 21 }; 22 23 private _onReceivedListener: Subscription | undefined; 24 private _onResponseReceivedListener: Subscription | undefined; 25 26 // See: https://github.com/expo/expo/pull/10229#discussion_r490961694 27 // eslint-disable-next-line @typescript-eslint/ban-types 28 constructor(props: {}) { 29 super(props); 30 this.state = {}; 31 } 32 33 componentDidMount() { 34 if (Platform.OS !== 'web') { 35 this._onReceivedListener = Notifications.addNotificationReceivedListener( 36 this._handelReceivedNotification 37 ); 38 this._onResponseReceivedListener = Notifications.addNotificationResponseReceivedListener( 39 this._handelNotificationResponseReceived 40 ); 41 // Using the same category as in `registerForPushNotificationsAsync` 42 Notifications.setNotificationCategoryAsync('welcome', [ 43 { 44 buttonTitle: `Don't open app`, 45 identifier: 'first-button', 46 options: { 47 opensAppToForeground: false, 48 }, 49 }, 50 { 51 buttonTitle: 'Respond with text', 52 identifier: 'second-button-with-text', 53 textInput: { 54 submitButtonTitle: 'Submit button', 55 placeholder: 'Placeholder text', 56 }, 57 }, 58 { 59 buttonTitle: 'Open app', 60 identifier: 'third-button', 61 options: { 62 opensAppToForeground: true, 63 }, 64 }, 65 ]) 66 .then(category => console.log('Notification category set', category)) 67 .catch(error => console.warn('Could not have set notification category', error)); 68 } 69 } 70 71 componentWillUnmount() { 72 this._onReceivedListener?.remove(); 73 this._onResponseReceivedListener?.remove(); 74 } 75 76 render() { 77 return ( 78 <ScrollView contentContainerStyle={{ padding: 10, paddingBottom: 40 }}> 79 <HeadingText>Local Notifications</HeadingText> 80 <ListButton 81 onPress={this._LEGACY_presentLocalNotificationAsync} 82 title="[Legacy] Present a notification immediately" 83 /> 84 <ListButton 85 onPress={this._presentLocalNotificationAsync} 86 title="Present a notification immediately" 87 /> 88 <ListButton 89 onPress={this._scheduleLocalNotificationAsync} 90 title="Schedule notification for 10 seconds from now" 91 /> 92 <ListButton 93 onPress={this._scheduleLocalNotificationAndCancelAsync} 94 title="Schedule notification for 10 seconds from now and then cancel it immediately" 95 /> 96 <ListButton 97 onPress={Notifications.cancelAllScheduledNotificationsAsync} 98 title="Cancel all scheduled notifications" 99 /> 100 101 <HeadingText>Push Notifications</HeadingText> 102 <ListButton onPress={this._sendNotificationAsync} title="Send me a push notification" /> 103 104 <HeadingText>Badge Number</HeadingText> 105 <ListButton 106 onPress={this._incrementIconBadgeNumberAsync} 107 title="Increment the app icon's badge number" 108 /> 109 <ListButton onPress={this._clearIconBadgeAsync} title="Clear the app icon's badge number" /> 110 111 <HeadingText>Dismissing notifications</HeadingText> 112 <ListButton 113 onPress={this._countPresentedNotifications} 114 title="Count presented notifications" 115 /> 116 <ListButton onPress={this._dismissSingle} title="Dismiss a single notification" /> 117 118 <ListButton onPress={this._dismissAll} title="Dismiss all notifications" /> 119 120 {this.state.lastNotifications && ( 121 <MonoText containerStyle={{ marginBottom: 20 }}> 122 {JSON.stringify(this.state.lastNotifications, null, 2)} 123 </MonoText> 124 )} 125 126 <HeadingText>Notification Permissions</HeadingText> 127 <ListButton onPress={this.getPermissionsAsync} title="Get permissions" /> 128 <ListButton onPress={this.requestPermissionsAsync} title="Request permissions" /> 129 130 <HeadingText>Notification triggers debugging</HeadingText> 131 <ListButton 132 onPress={() => 133 Notifications.getNextTriggerDateAsync({ seconds: 10 }).then(timestamp => 134 alert(new Date(timestamp!)) 135 ) 136 } 137 title="Get next date for time interval + 10 seconds" 138 /> 139 <ListButton 140 onPress={() => 141 Notifications.getNextTriggerDateAsync({ 142 hour: 9, 143 minute: 0, 144 repeats: true, 145 }).then(timestamp => alert(new Date(timestamp!))) 146 } 147 title="Get next date for 9 AM" 148 /> 149 <ListButton 150 onPress={() => 151 Notifications.getNextTriggerDateAsync({ 152 hour: 9, 153 minute: 0, 154 weekday: 1, 155 repeats: true, 156 }).then(timestamp => alert(new Date(timestamp!))) 157 } 158 title="Get next date for Sunday, 9 AM" 159 /> 160 </ScrollView> 161 ); 162 } 163 164 _handelReceivedNotification = (notification: Notifications.Notification) => { 165 this.setState({ 166 lastNotifications: notification, 167 }); 168 }; 169 170 _handelNotificationResponseReceived = ( 171 notificationResponse: Notifications.NotificationResponse 172 ) => { 173 console.log({ notificationResponse }); 174 175 // Calling alert(message) immediately fails to show the alert on Android 176 // if after backgrounding the app and then clicking on a notification 177 // to foreground the app 178 setTimeout(() => Alert.alert('You clicked on the notification '), 1000); 179 }; 180 181 private getPermissionsAsync = async () => { 182 const permission = await Notifications.getPermissionsAsync(); 183 console.log('Get permission: ', permission); 184 alert(`Status: ${permission.status}`); 185 }; 186 187 private requestPermissionsAsync = async () => { 188 const permission = await Notifications.requestPermissionsAsync(); 189 alert(`Status: ${permission.status}`); 190 }; 191 192 _obtainUserFacingNotifPermissionsAsync = async () => { 193 let permission = await Notifications.getPermissionsAsync(); 194 if (permission.status !== 'granted') { 195 permission = await Notifications.requestPermissionsAsync(); 196 if (permission.status !== 'granted') { 197 Alert.alert(`We don't have permission to present notifications.`); 198 } 199 } 200 return permission; 201 }; 202 203 // This is the same thing as user-facing notifications in expo-notifications 204 _obtainRemoteNotifPermissionsAsync = async () => { 205 let permission = await Notifications.getPermissionsAsync(); 206 if (permission.status !== 'granted') { 207 permission = await Notifications.requestPermissionsAsync(); 208 if (permission.status !== 'granted') { 209 Alert.alert(`We don't have permission to receive remote notifications.`); 210 } 211 } 212 return permission; 213 }; 214 215 _presentLocalNotificationAsync = async () => { 216 await this._obtainUserFacingNotifPermissionsAsync(); 217 await Notifications.scheduleNotificationAsync({ 218 content: { 219 title: 'Here is a scheduled notification!', 220 body: 'This is the body', 221 data: { 222 hello: 'there', 223 future: 'self', 224 }, 225 sound: true, 226 }, 227 trigger: null, 228 }); 229 }; 230 231 _LEGACY_presentLocalNotificationAsync = async () => { 232 await this._obtainUserFacingNotifPermissionsAsync(); 233 await Notifications.presentNotificationAsync({ 234 title: 'Here is a local notification!', 235 body: 'This is the body', 236 data: { 237 hello: 'there', 238 }, 239 sound: true, 240 }); 241 }; 242 243 _scheduleLocalNotificationAsync = async () => { 244 await this._obtainUserFacingNotifPermissionsAsync(); 245 await Notifications.scheduleNotificationAsync({ 246 content: { 247 title: 'Here is a local notification!', 248 body: 'This is the body', 249 data: { 250 hello: 'there', 251 future: 'self', 252 }, 253 sound: true, 254 }, 255 trigger: { 256 seconds: 10, 257 }, 258 }); 259 }; 260 261 _scheduleLocalNotificationAndCancelAsync = async () => { 262 await this._obtainUserFacingNotifPermissionsAsync(); 263 const notificationId = await Notifications.scheduleNotificationAsync({ 264 content: { 265 title: 'This notification should not appear', 266 body: 'It should have been cancelled. :(', 267 sound: true, 268 }, 269 trigger: { 270 seconds: 10, 271 }, 272 }); 273 await Notifications.cancelScheduledNotificationAsync(notificationId); 274 }; 275 276 _incrementIconBadgeNumberAsync = async () => { 277 const currentNumber = await Notifications.getBadgeCountAsync(); 278 await Notifications.setBadgeCountAsync(currentNumber + 1); 279 const actualNumber = await Notifications.getBadgeCountAsync(); 280 Alert.alert(`Set the badge number to ${actualNumber}`); 281 }; 282 283 _clearIconBadgeAsync = async () => { 284 await Notifications.setBadgeCountAsync(0); 285 Alert.alert(`Cleared the badge`); 286 }; 287 288 _sendNotificationAsync = async () => { 289 const permission = await this._obtainRemoteNotifPermissionsAsync(); 290 if (permission.status === 'granted') { 291 registerForPushNotificationsAsync(); 292 } 293 }; 294 295 _countPresentedNotifications = async () => { 296 const presentedNotifications = await Notifications.getPresentedNotificationsAsync(); 297 Alert.alert(`You currently have ${presentedNotifications.length} notifications presented`); 298 }; 299 300 _dismissAll = async () => { 301 await Notifications.dismissAllNotificationsAsync(); 302 Alert.alert(`Notifications dismissed`); 303 }; 304 305 _dismissSingle = async () => { 306 const presentedNotifications = await Notifications.getPresentedNotificationsAsync(); 307 if (!presentedNotifications.length) { 308 Alert.alert(`No notifications to be dismissed`); 309 return; 310 } 311 312 const identifier = presentedNotifications[0].request.identifier; 313 await Notifications.dismissNotificationAsync(identifier); 314 Alert.alert(`Notification dismissed`); 315 }; 316} 317