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