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._scheduleLocalNotificationWithCustomSoundAsync} 94 title="Schedule notification with custom sound in 1 second (not supported in Expo Go)" 95 /> 96 <ListButton 97 onPress={this._scheduleLocalNotificationAndCancelAsync} 98 title="Schedule notification for 10 seconds from now and then cancel it immediately" 99 /> 100 <ListButton 101 onPress={Notifications.cancelAllScheduledNotificationsAsync} 102 title="Cancel all scheduled notifications" 103 /> 104 105 <HeadingText>Push Notifications</HeadingText> 106 <ListButton onPress={this._sendNotificationAsync} title="Send me a push notification" /> 107 108 <HeadingText>Badge Number</HeadingText> 109 <ListButton 110 onPress={this._incrementIconBadgeNumberAsync} 111 title="Increment the app icon's badge number" 112 /> 113 <ListButton onPress={this._clearIconBadgeAsync} title="Clear the app icon's badge number" /> 114 115 <HeadingText>Dismissing notifications</HeadingText> 116 <ListButton 117 onPress={this._countPresentedNotifications} 118 title="Count presented notifications" 119 /> 120 <ListButton onPress={this._dismissSingle} title="Dismiss a single notification" /> 121 122 <ListButton onPress={this._dismissAll} title="Dismiss all notifications" /> 123 124 {this.state.lastNotifications && ( 125 <MonoText containerStyle={{ marginBottom: 20 }}> 126 {JSON.stringify(this.state.lastNotifications, null, 2)} 127 </MonoText> 128 )} 129 130 <HeadingText>Notification Permissions</HeadingText> 131 <ListButton onPress={this.getPermissionsAsync} title="Get permissions" /> 132 <ListButton onPress={this.requestPermissionsAsync} title="Request permissions" /> 133 134 <HeadingText>Notification triggers debugging</HeadingText> 135 <ListButton 136 onPress={() => 137 Notifications.getNextTriggerDateAsync({ seconds: 10 }).then(timestamp => 138 alert(new Date(timestamp!)) 139 ) 140 } 141 title="Get next date for time interval + 10 seconds" 142 /> 143 <ListButton 144 onPress={() => 145 Notifications.getNextTriggerDateAsync({ 146 hour: 9, 147 minute: 0, 148 repeats: true, 149 }).then(timestamp => alert(new Date(timestamp!))) 150 } 151 title="Get next date for 9 AM" 152 /> 153 <ListButton 154 onPress={() => 155 Notifications.getNextTriggerDateAsync({ 156 hour: 9, 157 minute: 0, 158 weekday: 1, 159 repeats: true, 160 }).then(timestamp => alert(new Date(timestamp!))) 161 } 162 title="Get next date for Sunday, 9 AM" 163 /> 164 </ScrollView> 165 ); 166 } 167 168 _handelReceivedNotification = (notification: Notifications.Notification) => { 169 this.setState({ 170 lastNotifications: notification, 171 }); 172 }; 173 174 _handelNotificationResponseReceived = ( 175 notificationResponse: Notifications.NotificationResponse 176 ) => { 177 console.log({ notificationResponse }); 178 179 // Calling alert(message) immediately fails to show the alert on Android 180 // if after backgrounding the app and then clicking on a notification 181 // to foreground the app 182 setTimeout(() => Alert.alert('You clicked on the notification '), 1000); 183 }; 184 185 private getPermissionsAsync = async () => { 186 const permission = await Notifications.getPermissionsAsync(); 187 console.log('Get permission: ', permission); 188 alert(`Status: ${permission.status}`); 189 }; 190 191 private requestPermissionsAsync = async () => { 192 const permission = await Notifications.requestPermissionsAsync(); 193 alert(`Status: ${permission.status}`); 194 }; 195 196 _obtainUserFacingNotifPermissionsAsync = async () => { 197 let permission = await Notifications.getPermissionsAsync(); 198 if (permission.status !== 'granted') { 199 permission = await Notifications.requestPermissionsAsync(); 200 if (permission.status !== 'granted') { 201 Alert.alert(`We don't have permission to present notifications.`); 202 } 203 } 204 return permission; 205 }; 206 207 // This is the same thing as user-facing notifications in expo-notifications 208 _obtainRemoteNotifPermissionsAsync = async () => { 209 let permission = await Notifications.getPermissionsAsync(); 210 if (permission.status !== 'granted') { 211 permission = await Notifications.requestPermissionsAsync(); 212 if (permission.status !== 'granted') { 213 Alert.alert(`We don't have permission to receive remote notifications.`); 214 } 215 } 216 return permission; 217 }; 218 219 _presentLocalNotificationAsync = async () => { 220 await this._obtainUserFacingNotifPermissionsAsync(); 221 await Notifications.scheduleNotificationAsync({ 222 content: { 223 title: 'Here is a scheduled notification!', 224 body: 'This is the body', 225 data: { 226 hello: 'there', 227 future: 'self', 228 }, 229 sound: true, 230 }, 231 trigger: null, 232 }); 233 }; 234 235 _LEGACY_presentLocalNotificationAsync = async () => { 236 await this._obtainUserFacingNotifPermissionsAsync(); 237 await Notifications.presentNotificationAsync({ 238 title: 'Here is a local notification!', 239 body: 'This is the body', 240 data: { 241 hello: 'there', 242 }, 243 sound: true, 244 }); 245 }; 246 247 _scheduleLocalNotificationAsync = async () => { 248 await this._obtainUserFacingNotifPermissionsAsync(); 249 await Notifications.scheduleNotificationAsync({ 250 content: { 251 title: 'Here is a local notification!', 252 body: 'This is the body', 253 data: { 254 hello: 'there', 255 future: 'self', 256 }, 257 sound: true, 258 }, 259 trigger: { 260 seconds: 10, 261 }, 262 }); 263 }; 264 265 _scheduleLocalNotificationWithCustomSoundAsync = async () => { 266 await this._obtainUserFacingNotifPermissionsAsync(); 267 // Prepare the notification channel 268 await Notifications.setNotificationChannelAsync('custom-sound', { 269 name: 'Notification with custom sound', 270 importance: Notifications.AndroidImportance.HIGH, 271 sound: 'cat.wav', // <- for Android 8.0+ 272 }); 273 await Notifications.scheduleNotificationAsync({ 274 content: { 275 title: 'Here is a local notification!', 276 body: 'This is the body', 277 data: { 278 hello: 'there', 279 future: 'self', 280 }, 281 sound: 'cat.wav', 282 }, 283 trigger: { 284 channelId: 'custom-sound', 285 seconds: 1, 286 }, 287 }); 288 }; 289 290 _scheduleLocalNotificationAndCancelAsync = async () => { 291 await this._obtainUserFacingNotifPermissionsAsync(); 292 const notificationId = await Notifications.scheduleNotificationAsync({ 293 content: { 294 title: 'This notification should not appear', 295 body: 'It should have been cancelled. :(', 296 sound: true, 297 }, 298 trigger: { 299 seconds: 10, 300 }, 301 }); 302 await Notifications.cancelScheduledNotificationAsync(notificationId); 303 }; 304 305 _incrementIconBadgeNumberAsync = async () => { 306 const currentNumber = await Notifications.getBadgeCountAsync(); 307 await Notifications.setBadgeCountAsync(currentNumber + 1); 308 const actualNumber = await Notifications.getBadgeCountAsync(); 309 Alert.alert(`Set the badge number to ${actualNumber}`); 310 }; 311 312 _clearIconBadgeAsync = async () => { 313 await Notifications.setBadgeCountAsync(0); 314 Alert.alert(`Cleared the badge`); 315 }; 316 317 _sendNotificationAsync = async () => { 318 const permission = await this._obtainRemoteNotifPermissionsAsync(); 319 if (permission.status === 'granted') { 320 registerForPushNotificationsAsync(); 321 } 322 }; 323 324 _countPresentedNotifications = async () => { 325 const presentedNotifications = await Notifications.getPresentedNotificationsAsync(); 326 Alert.alert(`You currently have ${presentedNotifications.length} notifications presented`); 327 }; 328 329 _dismissAll = async () => { 330 await Notifications.dismissAllNotificationsAsync(); 331 Alert.alert(`Notifications dismissed`); 332 }; 333 334 _dismissSingle = async () => { 335 const presentedNotifications = await Notifications.getPresentedNotificationsAsync(); 336 if (!presentedNotifications.length) { 337 Alert.alert(`No notifications to be dismissed`); 338 return; 339 } 340 341 const identifier = presentedNotifications[0].request.identifier; 342 await Notifications.dismissNotificationAsync(identifier); 343 Alert.alert(`Notification dismissed`); 344 }; 345} 346