expo-notifications 1.0.0-canary-20241008-90b13ad → 1.0.0-canary-20241018-bf4b2f7-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/android/build.gradle +1 -0
  3. package/android/src/main/java/expo/modules/notifications/notifications/NotificationSerializer.java +5 -53
  4. package/android/src/main/java/expo/modules/notifications/notifications/channels/managers/AndroidXNotificationsChannelManager.java +4 -1
  5. package/android/src/main/java/expo/modules/notifications/notifications/debug/DebugLogging.kt +1 -1
  6. package/android/src/main/java/expo/modules/notifications/notifications/interfaces/NotificationTrigger.kt +19 -0
  7. package/android/src/main/java/expo/modules/notifications/notifications/model/triggers/FirebaseNotificationTrigger.kt +11 -3
  8. package/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/BaseNotificationBuilder.kt +1 -1
  9. package/android/src/main/java/expo/modules/notifications/notifications/scheduling/NotificationScheduler.kt +10 -10
  10. package/android/src/main/java/expo/modules/notifications/notifications/triggers/NotificationTriggers.kt +195 -0
  11. package/android/src/main/java/expo/modules/notifications/service/delegates/ExpoPresentationDelegate.kt +1 -1
  12. package/android/src/main/java/expo/modules/notifications/service/delegates/ExpoSchedulingDelegate.kt +5 -0
  13. package/build/NotificationPermissions.d.ts +3 -13
  14. package/build/NotificationPermissions.d.ts.map +1 -1
  15. package/build/NotificationPermissions.js +1 -15
  16. package/build/NotificationPermissions.js.map +1 -1
  17. package/build/Notifications.types.d.ts +1 -2
  18. package/build/Notifications.types.d.ts.map +1 -1
  19. package/build/Notifications.types.js +1 -0
  20. package/build/Notifications.types.js.map +1 -1
  21. package/ios/EXNotifications/Permissions/EXUserFacingNotificationsPermissionsRequester.m +1 -1
  22. package/package.json +7 -7
  23. package/src/NotificationPermissions.ts +2 -23
  24. package/src/Notifications.types.ts +4 -11
  25. package/android/src/main/java/expo/modules/notifications/notifications/interfaces/NotificationTrigger.java +0 -16
  26. package/android/src/main/java/expo/modules/notifications/notifications/triggers/ChannelAwareTrigger.java +0 -49
  27. package/android/src/main/java/expo/modules/notifications/notifications/triggers/DailyTrigger.java +0 -76
  28. package/android/src/main/java/expo/modules/notifications/notifications/triggers/DateTrigger.java +0 -64
  29. package/android/src/main/java/expo/modules/notifications/notifications/triggers/MonthlyTrigger.java +0 -85
  30. package/android/src/main/java/expo/modules/notifications/notifications/triggers/TimeIntervalTrigger.java +0 -87
  31. package/android/src/main/java/expo/modules/notifications/notifications/triggers/WeeklyTrigger.java +0 -85
  32. package/android/src/main/java/expo/modules/notifications/notifications/triggers/YearlyTrigger.java +0 -94
package/CHANGELOG.md CHANGED
@@ -14,6 +14,10 @@
14
14
 
15
15
  ### 🐛 Bug fixes
16
16
 
17
+ - [ios] fix crash if expo-update reload happens while Notifications.requestPermissionsAsync() is showing native dialog ([#32096](https://github.com/expo/expo/pull/32096) by [@mfazekas](https://github.com/mfazekas))
18
+ - [android] `createNotificationChannel` could return incorrect channel information ([#32000](https://github.com/expo/expo/pull/32000) by [@vonovak](https://github.com/vonovak))
19
+ - [android] fix notifications with `ChannelAwareTrigger` not being presented ([#31999](https://github.com/expo/expo/pull/31999) by [@vonovak](https://github.com/vonovak))
20
+ - export `PermissionStatus` as value, not as type ([#31968](https://github.com/expo/expo/pull/31968) by [@vonovak](https://github.com/vonovak))
17
21
  - throw improved error on invalid subscription in removeNotificationSubscription ([#31842](https://github.com/expo/expo/pull/31842) by [@vonovak](https://github.com/vonovak))
18
22
  - [android] fix notifications actions not being presented ([#31795](https://github.com/expo/expo/pull/31795) by [@vonovak](https://github.com/vonovak))
19
23
  - Add missing `react` and `react-native` peer dependencies for isolated modules. ([#30478](https://github.com/expo/expo/pull/30478) by [@byCedric](https://github.com/byCedric))
@@ -21,9 +25,12 @@
21
25
 
22
26
  ### 💡 Others
23
27
 
28
+ - [android] refactor trigger serialization ([#32032](https://github.com/expo/expo/pull/32032) by [@vonovak](https://github.com/vonovak))
29
+ - [android] simplify DateTrigger ([#32002](https://github.com/expo/expo/pull/32002) by [@vonovak](https://github.com/vonovak))
24
30
  - [android] refactor ExpoNotificationBuilder ([#31838](https://github.com/expo/expo/pull/31838) by [@vonovak](https://github.com/vonovak))
25
31
  - Warn about limited support in Expo Go ([#31573](https://github.com/expo/expo/pull/31573) by [@vonovak](https://github.com/vonovak))
26
32
  - Keep using the legacy event emitter as the module is not fully migrated to Expo Modules API. ([#28946](https://github.com/expo/expo/pull/28946) by [@tsapeta](https://github.com/tsapeta))
33
+ - [Android] Convert trigger Java classes to Kotlin. ([#31856](https://github.com/expo/expo/pull/31856) by [@douglowder](https://github.com/douglowder))
27
34
 
28
35
  ## 0.28.17 - 2024-09-17
29
36
 
@@ -286,6 +293,7 @@ _This version does not introduce any user-facing changes._
286
293
 
287
294
  ### 🛠 Breaking changes
288
295
 
296
+ - remove `usePermissions` hook ([#31905](https://github.com/expo/expo/pull/31905) by [@vonovak](https://github.com/vonovak))
289
297
  - [android] Set the "notification number" (sometimes used to increment badge count on some launchers) from the notification payload `badge` field. ([#17171](https://github.com/expo/expo/pull/17171) by [@danstepanov](https://github.com/danstepanov))
290
298
 
291
299
  ### 🐛 Bug fixes
@@ -1,4 +1,5 @@
1
1
  apply plugin: 'com.android.library'
2
+ apply plugin: 'kotlin-parcelize'
2
3
 
3
4
  group = 'host.exp.exponent'
4
5
  version = '0.28.3'
@@ -58,11 +58,12 @@ public class NotificationSerializer {
58
58
  public static Bundle toBundle(NotificationRequest request) {
59
59
  Bundle serializedRequest = new Bundle();
60
60
  serializedRequest.putString("identifier", request.getIdentifier());
61
- serializedRequest.putBundle("trigger", toBundle(request.getTrigger()));
61
+ NotificationTrigger requestTrigger = request.getTrigger();
62
+ serializedRequest.putBundle("trigger", requestTrigger == null ? null : requestTrigger.toBundle());
62
63
  Bundle content = toBundle(request.getContent());
63
64
  Bundle existingContentData = content.getBundle("data");
64
65
  if (existingContentData == null) {
65
- if(request.getTrigger() instanceof FirebaseNotificationTrigger trigger) {
66
+ if(requestTrigger instanceof FirebaseNotificationTrigger trigger) {
66
67
  RemoteMessage message = trigger.getRemoteMessage();
67
68
  RemoteMessage.Notification notification = message.getNotification();
68
69
  Map<String, String> data = message.getData();
@@ -77,8 +78,8 @@ public class NotificationSerializer {
77
78
  content.putBundle("data", toBundle(data));
78
79
  }
79
80
  } else if(
80
- request.getTrigger() instanceof SchedulableNotificationTrigger ||
81
- request.getTrigger() == null
81
+ requestTrigger instanceof SchedulableNotificationTrigger ||
82
+ requestTrigger == null
82
83
  ) {
83
84
  JSONObject body = request.getContent().getBody();
84
85
  if (body != null) {
@@ -187,55 +188,6 @@ public class NotificationSerializer {
187
188
  return result;
188
189
  }
189
190
 
190
- private static Bundle toBundle(@Nullable NotificationTrigger trigger) {
191
- if (trigger == null) {
192
- return null;
193
- }
194
- Bundle bundle = new Bundle();
195
- if (trigger instanceof FirebaseNotificationTrigger) {
196
- bundle.putString("type", "push");
197
- bundle.putBundle("remoteMessage", RemoteMessageSerializer.toBundle(((FirebaseNotificationTrigger) trigger).getRemoteMessage()));
198
- } else if (trigger instanceof TimeIntervalTrigger) {
199
- bundle.putString("type", "timeInterval");
200
- bundle.putBoolean("repeats", ((TimeIntervalTrigger) trigger).isRepeating());
201
- bundle.putLong("seconds", ((TimeIntervalTrigger) trigger).getTimeInterval());
202
- } else if (trigger instanceof DateTrigger) {
203
- bundle.putString("type", "date");
204
- bundle.putBoolean("repeats", false);
205
- bundle.putLong("value", ((DateTrigger) trigger).getTriggerDate().getTime());
206
- } else if (trigger instanceof DailyTrigger) {
207
- bundle.putString("type", "daily");
208
- bundle.putInt("hour", ((DailyTrigger) trigger).getHour());
209
- bundle.putInt("minute", ((DailyTrigger) trigger).getMinute());
210
- } else if (trigger instanceof WeeklyTrigger) {
211
- bundle.putString("type", "weekly");
212
- bundle.putInt("weekday", ((WeeklyTrigger) trigger).getWeekday());
213
- bundle.putInt("hour", ((WeeklyTrigger) trigger).getHour());
214
- bundle.putInt("minute", ((WeeklyTrigger) trigger).getMinute());
215
- } else if (trigger instanceof MonthlyTrigger) {
216
- bundle.putString("type", "monthly");
217
- bundle.putInt("day", ((MonthlyTrigger) trigger).getDay());
218
- bundle.putInt("hour", ((MonthlyTrigger) trigger).getHour());
219
- bundle.putInt("minute", ((MonthlyTrigger) trigger).getMinute());
220
- } else if (trigger instanceof YearlyTrigger) {
221
- bundle.putString("type", "yearly");
222
- bundle.putInt("day", ((YearlyTrigger) trigger).getDay());
223
- bundle.putInt("month", ((YearlyTrigger) trigger).getMonth());
224
- bundle.putInt("hour", ((YearlyTrigger) trigger).getHour());
225
- bundle.putInt("minute", ((YearlyTrigger) trigger).getMinute());
226
- } else {
227
- bundle.putString("type", "unknown");
228
- }
229
- bundle.putString("channelId", getChannelId(trigger));
230
-
231
- return bundle;
232
- }
233
-
234
- @Nullable
235
- private static String getChannelId(NotificationTrigger trigger) {
236
- return trigger.getNotificationChannel();
237
- }
238
-
239
191
  @NotNull
240
192
  public static Bundle toResponseBundleFromExtras(Bundle extras) {
241
193
  Bundle serializedContent = new Bundle();
@@ -80,7 +80,10 @@ public class AndroidXNotificationsChannelManager implements NotificationsChannel
80
80
  NotificationChannel channel = new NotificationChannel(channelId, name, importance);
81
81
  configureChannelWithOptions(channel, channelOptions);
82
82
  mNotificationManager.createNotificationChannel(channel);
83
- return channel;
83
+ // We return the channel given by mNotificationManager, not the one we created.
84
+ // see "Note that the created channel may differ from this value." ("this value" = the one we provided)
85
+ // https://developer.android.com/reference/android/app/NotificationManager#createNotificationChannel(android.app.NotificationChannel)
86
+ return mNotificationManager.getNotificationChannel(channelId);
84
87
  }
85
88
 
86
89
  // Processing options
@@ -73,7 +73,7 @@ object DebugLogging {
73
73
  notification.notificationRequest.content.subText: ${notification.notificationRequest.content.subText}
74
74
  notification.notificationRequest.content.text: ${notification.notificationRequest.content.text}
75
75
  notification.notificationRequest.content.sound: ${notification.notificationRequest.content.soundName}
76
- notification.notificationRequest.content.channelID: ${notification.notificationRequest.trigger.notificationChannel}
76
+ notification.notificationRequest.content.channelID: ${notification.notificationRequest.trigger.getNotificationChannel()}
77
77
  notification.notificationRequest.content.body: ${notification.notificationRequest.content.body}
78
78
  notification.notificationRequest.content.color: ${notification.notificationRequest.content.color}
79
79
  notification.notificationRequest.content.vibrationPattern: ${notification.notificationRequest.content.vibrationPattern?.contentToString()}
@@ -0,0 +1,19 @@
1
+ package expo.modules.notifications.notifications.interfaces
2
+
3
+ import android.os.Bundle
4
+ import android.os.Parcelable
5
+
6
+ /**
7
+ * An interface specifying source of the notification, to be implemented
8
+ * by concrete classes.
9
+ */
10
+ interface NotificationTrigger : Parcelable {
11
+ // these are functions so that we're absolutely sure @Parcelize doesn't try to parcelize them
12
+ // (as opposed to if they were properties)
13
+
14
+ fun getNotificationChannel(): String? {
15
+ return null
16
+ }
17
+
18
+ fun toBundle(): Bundle
19
+ }
@@ -1,30 +1,38 @@
1
1
  package expo.modules.notifications.notifications.model.triggers
2
2
 
3
3
  import android.os.Build
4
+ import android.os.Bundle
4
5
  import android.os.Parcel
5
6
  import android.os.Parcelable
6
7
  import androidx.annotation.RequiresApi
8
+ import androidx.core.os.bundleOf
7
9
  import com.google.firebase.messaging.RemoteMessage
10
+ import expo.modules.notifications.notifications.RemoteMessageSerializer
8
11
  import expo.modules.notifications.notifications.interfaces.NotificationTrigger
9
12
 
10
13
  /**
11
14
  * A trigger representing an incoming remote Firebase notification.
12
15
  */
13
- class FirebaseNotificationTrigger(private val remoteMessage: RemoteMessage) : NotificationTrigger {
16
+ class FirebaseNotificationTrigger(val remoteMessage: RemoteMessage) : NotificationTrigger {
14
17
 
15
18
  private constructor(parcel: Parcel) : this(
16
19
  parcel.readParcelable(FirebaseNotificationTrigger::class.java.classLoader)
17
20
  ?: throw IllegalArgumentException("RemoteMessage from readParcelable must not be null")
18
21
  )
19
22
 
20
- fun getRemoteMessage(): RemoteMessage = remoteMessage
21
-
22
23
  @RequiresApi(api = Build.VERSION_CODES.O)
23
24
  override fun getNotificationChannel(): String? {
24
25
  val channelId = remoteMessage.notification?.channelId ?: remoteMessage.data["channelId"]
25
26
  return channelId ?: super.getNotificationChannel()
26
27
  }
27
28
 
29
+ override fun toBundle(): Bundle {
30
+ return bundleOf(
31
+ "type" to "push",
32
+ "remoteMessage" to RemoteMessageSerializer.toBundle(remoteMessage)
33
+ )
34
+ }
35
+
28
36
  override fun describeContents(): Int {
29
37
  return 0
30
38
  }
@@ -58,7 +58,7 @@ abstract class BaseNotificationBuilder protected constructor(protected val conte
58
58
  return fallbackNotificationChannel!!.id
59
59
  }
60
60
 
61
- val requestedChannelId = trigger.notificationChannel
61
+ val requestedChannelId = trigger.getNotificationChannel()
62
62
  ?: return fallbackNotificationChannel!!.id
63
63
 
64
64
  val channelForRequestedId =
@@ -157,14 +157,14 @@ open class NotificationScheduler : Module() {
157
157
  val seconds = params["seconds"] as? Number
158
158
  ?: throw InvalidArgumentException("Invalid value provided as interval of trigger.")
159
159
 
160
- TimeIntervalTrigger(seconds.toLong(), params.getBoolean("repeats"), channelId)
160
+ TimeIntervalTrigger(channelId, seconds.toLong(), params.getBoolean("repeats"))
161
161
  }
162
162
 
163
163
  "date" -> {
164
164
  val timestamp = params["timestamp"] as? Number
165
165
  ?: throw InvalidArgumentException("Invalid value provided as date of trigger.")
166
166
 
167
- DateTrigger(timestamp.toLong(), channelId)
167
+ DateTrigger(channelId, timestamp.toLong())
168
168
  }
169
169
 
170
170
  "daily" -> {
@@ -176,9 +176,9 @@ open class NotificationScheduler : Module() {
176
176
  }
177
177
 
178
178
  DailyTrigger(
179
+ channelId,
179
180
  hour.toInt(),
180
- minute.toInt(),
181
- channelId
181
+ minute.toInt()
182
182
  )
183
183
  }
184
184
 
@@ -191,10 +191,10 @@ open class NotificationScheduler : Module() {
191
191
  throw InvalidArgumentException("Invalid value(s) provided for weekly trigger.")
192
192
  }
193
193
  WeeklyTrigger(
194
+ channelId,
194
195
  weekday.toInt(),
195
196
  hour.toInt(),
196
- minute.toInt(),
197
- channelId
197
+ minute.toInt()
198
198
  )
199
199
  }
200
200
 
@@ -208,10 +208,10 @@ open class NotificationScheduler : Module() {
208
208
  }
209
209
 
210
210
  MonthlyTrigger(
211
+ channelId,
211
212
  day.toInt(),
212
213
  hour.toInt(),
213
- minute.toInt(),
214
- channelId
214
+ minute.toInt()
215
215
  )
216
216
  }
217
217
 
@@ -226,11 +226,11 @@ open class NotificationScheduler : Module() {
226
226
  }
227
227
 
228
228
  YearlyTrigger(
229
+ channelId,
229
230
  day.toInt(),
230
231
  month.toInt(),
231
232
  hour.toInt(),
232
- minute.toInt(),
233
- channelId
233
+ minute.toInt()
234
234
  )
235
235
  }
236
236
 
@@ -0,0 +1,195 @@
1
+ package expo.modules.notifications.notifications.triggers
2
+
3
+ import android.os.Bundle
4
+ import androidx.core.os.bundleOf
5
+ import expo.modules.notifications.notifications.interfaces.NotificationTrigger
6
+ import expo.modules.notifications.notifications.interfaces.SchedulableNotificationTrigger
7
+ import kotlinx.parcelize.IgnoredOnParcel
8
+ import kotlinx.parcelize.Parcelize
9
+ import java.io.Serializable
10
+ import java.util.Calendar
11
+ import java.util.Date
12
+
13
+ @Parcelize
14
+ open class ChannelAwareTrigger(open val channelId: String?) :
15
+ NotificationTrigger, Serializable {
16
+
17
+ override fun describeContents(): Int = 0
18
+
19
+ override fun getNotificationChannel() = channelId
20
+
21
+ override fun toBundle() = bundleWithChannelId()
22
+
23
+ protected fun bundleWithChannelId(vararg pairs: Pair<String, Any?>): Bundle {
24
+ return bundleOf("channelId" to channelId, *pairs)
25
+ }
26
+ }
27
+
28
+ /**
29
+ * A schedulable trigger representing a notification to be scheduled once per day.
30
+ */
31
+ @Parcelize
32
+ class DailyTrigger(override val channelId: String?, val hour: Int, val minute: Int) : ChannelAwareTrigger(channelId), SchedulableNotificationTrigger {
33
+ override fun toBundle() = bundleWithChannelId(
34
+ "type" to "daily",
35
+ "hour" to hour,
36
+ "minute" to minute
37
+ )
38
+
39
+ override fun nextTriggerDate(): Date? {
40
+ val nextTriggerDate = Calendar.getInstance()
41
+ nextTriggerDate[Calendar.HOUR_OF_DAY] = hour
42
+ nextTriggerDate[Calendar.MINUTE] = minute
43
+ nextTriggerDate[Calendar.SECOND] = 0
44
+ nextTriggerDate[Calendar.MILLISECOND] = 0
45
+ val rightNow = Calendar.getInstance()
46
+ if (nextTriggerDate.before(rightNow)) {
47
+ nextTriggerDate.add(Calendar.DATE, 1)
48
+ }
49
+ return nextTriggerDate.time
50
+ }
51
+ }
52
+
53
+ /**
54
+ * A schedulable trigger representing notification to be scheduled only once at a given moment of time.
55
+ */
56
+ @Parcelize
57
+ class DateTrigger(override val channelId: String?, val timestamp: Long) : ChannelAwareTrigger(channelId), SchedulableNotificationTrigger {
58
+
59
+ override fun toBundle() = bundleWithChannelId(
60
+ "type" to "date",
61
+ "repeats" to false,
62
+ "value" to timestamp
63
+ )
64
+
65
+ override fun nextTriggerDate(): Date? {
66
+ val now = Date()
67
+ val triggerDate = Date(timestamp)
68
+
69
+ if (triggerDate.before(now)) {
70
+ return null
71
+ }
72
+
73
+ return triggerDate
74
+ }
75
+ }
76
+
77
+ /**
78
+ * A schedulable trigger representing a notification to be scheduled once per month.
79
+ */
80
+ @Parcelize
81
+ class MonthlyTrigger(override val channelId: String?, val day: Int, val hour: Int, val minute: Int) : ChannelAwareTrigger(channelId), SchedulableNotificationTrigger {
82
+ override fun toBundle() = bundleWithChannelId(
83
+ "type" to "monthly",
84
+ "day" to day,
85
+ "hour" to hour,
86
+ "minute" to minute
87
+ )
88
+
89
+ override fun nextTriggerDate(): Date? {
90
+ val nextTriggerDate = Calendar.getInstance()
91
+ nextTriggerDate[Calendar.DATE] = day
92
+ nextTriggerDate[Calendar.HOUR_OF_DAY] = hour
93
+ nextTriggerDate[Calendar.MINUTE] = minute
94
+ nextTriggerDate[Calendar.SECOND] = 0
95
+ nextTriggerDate[Calendar.MILLISECOND] = 0
96
+ val rightNow = Calendar.getInstance()
97
+ if (nextTriggerDate.before(rightNow)) {
98
+ nextTriggerDate.add(Calendar.MONTH, 1)
99
+ }
100
+ return nextTriggerDate.time
101
+ }
102
+ }
103
+
104
+ /**
105
+ * A schedulable trigger representing notification to be scheduled after X milliseconds,
106
+ * optionally repeating.
107
+ *
108
+ *
109
+ * *Note: The implementation ensures that the trigger times do not drift away too much from the
110
+ * * initial time, so eg. a trigger started at 11111000 time repeated every 1000 ms should always
111
+ * * trigger around …000 timestamp.*
112
+ */
113
+ @Parcelize
114
+ class TimeIntervalTrigger(override val channelId: String?, val timeInterval: Long, val isRepeating: Boolean) : ChannelAwareTrigger(channelId), SchedulableNotificationTrigger {
115
+ @IgnoredOnParcel
116
+ private var triggerDate = Date(Date().time + timeInterval * 1000)
117
+
118
+ override fun toBundle() = bundleWithChannelId(
119
+ "type" to "timeInterval",
120
+ "repeats" to isRepeating,
121
+ "seconds" to timeInterval
122
+ )
123
+
124
+ override fun nextTriggerDate(): Date? {
125
+ val now = Date()
126
+
127
+ if (isRepeating) {
128
+ while (triggerDate.before(now)) {
129
+ triggerDate.time += timeInterval * 1000
130
+ }
131
+ }
132
+
133
+ if (triggerDate.before(now)) {
134
+ return null
135
+ }
136
+
137
+ return triggerDate
138
+ }
139
+ }
140
+
141
+ /**
142
+ * A schedulable trigger representing a notification to be scheduled once per week.
143
+ */
144
+ @Parcelize
145
+ class WeeklyTrigger(override val channelId: String?, val weekday: Int, val hour: Int, val minute: Int) : ChannelAwareTrigger(channelId), SchedulableNotificationTrigger {
146
+ override fun toBundle() = bundleWithChannelId(
147
+ "type" to "weekly",
148
+ "weekday" to weekday,
149
+ "hour" to hour,
150
+ "minute" to minute
151
+ )
152
+
153
+ override fun nextTriggerDate(): Date? {
154
+ val nextTriggerDate = Calendar.getInstance()
155
+ nextTriggerDate[Calendar.DAY_OF_WEEK] = weekday
156
+ nextTriggerDate[Calendar.HOUR_OF_DAY] = hour
157
+ nextTriggerDate[Calendar.MINUTE] = minute
158
+ nextTriggerDate[Calendar.SECOND] = 0
159
+ nextTriggerDate[Calendar.MILLISECOND] = 0
160
+ val rightNow = Calendar.getInstance()
161
+ if (nextTriggerDate.before(rightNow)) {
162
+ nextTriggerDate.add(Calendar.DAY_OF_WEEK_IN_MONTH, 1)
163
+ }
164
+ return nextTriggerDate.time
165
+ }
166
+ }
167
+
168
+ /**
169
+ * A schedulable trigger representing a notification to be scheduled once per year.
170
+ */
171
+ @Parcelize
172
+ class YearlyTrigger(override val channelId: String?, val day: Int, val month: Int, val hour: Int, val minute: Int) : ChannelAwareTrigger(channelId), SchedulableNotificationTrigger {
173
+ override fun toBundle() = bundleWithChannelId(
174
+ "type" to "yearly",
175
+ "day" to day,
176
+ "month" to month,
177
+ "hour" to hour,
178
+ "minute" to minute
179
+ )
180
+
181
+ override fun nextTriggerDate(): Date? {
182
+ val nextTriggerDate = Calendar.getInstance()
183
+ nextTriggerDate[Calendar.DATE] = day
184
+ nextTriggerDate[Calendar.MONTH] = month
185
+ nextTriggerDate[Calendar.HOUR_OF_DAY] = hour
186
+ nextTriggerDate[Calendar.MINUTE] = minute
187
+ nextTriggerDate[Calendar.SECOND] = 0
188
+ nextTriggerDate[Calendar.MILLISECOND] = 0
189
+ val rightNow = Calendar.getInstance()
190
+ if (nextTriggerDate.before(rightNow)) {
191
+ nextTriggerDate.add(Calendar.YEAR, 1)
192
+ }
193
+ return nextTriggerDate.time
194
+ }
195
+ }
@@ -115,7 +115,7 @@ open class ExpoPresentationDelegate(
115
115
 
116
116
  private fun getNotificationSoundUri(notification: Notification): Uri? {
117
117
  return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
118
- notification.notificationRequest.trigger.notificationChannel?.let {
118
+ notification.notificationRequest.trigger.getNotificationChannel()?.let {
119
119
  notificationManager.getNotificationChannel(it)?.sound
120
120
  }
121
121
  } else {
@@ -9,6 +9,7 @@ import androidx.core.app.AlarmManagerCompat
9
9
  import expo.modules.notifications.notifications.interfaces.SchedulableNotificationTrigger
10
10
  import expo.modules.notifications.notifications.model.Notification
11
11
  import expo.modules.notifications.notifications.model.NotificationRequest
12
+ import expo.modules.notifications.notifications.triggers.ChannelAwareTrigger
12
13
  import expo.modules.notifications.service.NotificationsService
13
14
  import expo.modules.notifications.service.interfaces.SchedulingDelegate
14
15
  import java.io.IOException
@@ -50,6 +51,10 @@ class ExpoSchedulingDelegate(protected val context: Context) : SchedulingDelegat
50
51
  }
51
52
 
52
53
  if (request.trigger !is SchedulableNotificationTrigger) {
54
+ if (request.trigger is ChannelAwareTrigger) {
55
+ NotificationsService.receive(context, Notification(request))
56
+ return
57
+ }
53
58
  throw IllegalArgumentException("Notification request \"${request.identifier}\" does not have a schedulable trigger (it's ${request.trigger}). Refusing to schedule.")
54
59
  }
55
60
 
@@ -1,4 +1,4 @@
1
- import { NotificationPermissionsRequest, NotificationPermissionsStatus } from './NotificationPermissions.types';
1
+ import { NotificationPermissionsRequest } from './NotificationPermissions.types';
2
2
  /**
3
3
  * Calling this function checks current permissions settings related to notifications.
4
4
  * It lets you verify whether the app is currently allowed to display alerts, play sounds, etc.
@@ -18,7 +18,7 @@ import { NotificationPermissionsRequest, NotificationPermissionsStatus } from '.
18
18
  * ```
19
19
  * @header permissions
20
20
  */
21
- export declare function getPermissionsAsync(): Promise<NotificationPermissionsStatus>;
21
+ export declare function getPermissionsAsync(): Promise<import("./NotificationPermissions.types").NotificationPermissionsStatus>;
22
22
  /**
23
23
  * Prompts the user for notification permissions according to request. **Request defaults to asking the user to allow displaying alerts,
24
24
  * setting badge count and playing sounds**.
@@ -41,15 +41,5 @@ export declare function getPermissionsAsync(): Promise<NotificationPermissionsSt
41
41
  * ```
42
42
  * @header permissions
43
43
  */
44
- export declare function requestPermissionsAsync(permissions?: NotificationPermissionsRequest): Promise<NotificationPermissionsStatus>;
45
- /**
46
- * Check or request permissions to send and receive push notifications.
47
- * This uses both `requestPermissionsAsync` and `getPermissionsAsync` to interact with the permissions.
48
- * @example
49
- * ```ts
50
- * const [permissionResponse, requestPermission] = Notifications.usePermissions();
51
- * ```
52
- * @header permission
53
- */
54
- export declare const usePermissions: (options?: import("expo-modules-core").PermissionHookOptions<NotificationPermissionsRequest> | undefined) => [NotificationPermissionsStatus | null, () => Promise<NotificationPermissionsStatus>, () => Promise<NotificationPermissionsStatus>];
44
+ export declare function requestPermissionsAsync(permissions?: NotificationPermissionsRequest): Promise<import("./NotificationPermissions.types").NotificationPermissionsStatus>;
55
45
  //# sourceMappingURL=NotificationPermissions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"NotificationPermissions.d.ts","sourceRoot":"","sources":["../src/NotificationPermissions.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,8BAA8B,EAC9B,6BAA6B,EAC9B,MAAM,iCAAiC,CAAC;AAGzC;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,mBAAmB,2CAMxC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,uBAAuB,CAAC,WAAW,CAAC,EAAE,8BAA8B,0CAczF;AAGD;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,iPAMzB,CAAC"}
1
+ {"version":3,"file":"NotificationPermissions.d.ts","sourceRoot":"","sources":["../src/NotificationPermissions.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,8BAA8B,EAAE,MAAM,iCAAiC,CAAC;AAGjF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,mBAAmB,qFAMxC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,uBAAuB,CAAC,WAAW,CAAC,EAAE,8BAA8B,oFAczF"}
@@ -1,4 +1,4 @@
1
- import { createPermissionHook, Platform, UnavailabilityError } from 'expo-modules-core';
1
+ import { Platform, UnavailabilityError } from 'expo-modules-core';
2
2
  import NotificationPermissionsModule from './NotificationPermissionsModule';
3
3
  /**
4
4
  * Calling this function checks current permissions settings related to notifications.
@@ -61,18 +61,4 @@ export async function requestPermissionsAsync(permissions) {
61
61
  const requestedPlatformPermissions = requestedPermissions[Platform.OS];
62
62
  return await NotificationPermissionsModule.requestPermissionsAsync(requestedPlatformPermissions);
63
63
  }
64
- // @needsAudit
65
- /**
66
- * Check or request permissions to send and receive push notifications.
67
- * This uses both `requestPermissionsAsync` and `getPermissionsAsync` to interact with the permissions.
68
- * @example
69
- * ```ts
70
- * const [permissionResponse, requestPermission] = Notifications.usePermissions();
71
- * ```
72
- * @header permission
73
- */
74
- export const usePermissions = createPermissionHook({
75
- requestMethod: requestPermissionsAsync,
76
- getMethod: getPermissionsAsync,
77
- });
78
64
  //# sourceMappingURL=NotificationPermissions.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"NotificationPermissions.js","sourceRoot":"","sources":["../src/NotificationPermissions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAMxF,OAAO,6BAA6B,MAAM,iCAAiC,CAAC;AAE5E;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB;IACvC,IAAI,CAAC,6BAA6B,CAAC,mBAAmB,EAAE;QACtD,MAAM,IAAI,mBAAmB,CAAC,eAAe,EAAE,qBAAqB,CAAC,CAAC;KACvE;IAED,OAAO,MAAM,6BAA6B,CAAC,mBAAmB,EAAE,CAAC;AACnE,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,WAA4C;IACxF,IAAI,CAAC,6BAA6B,CAAC,uBAAuB,EAAE;QAC1D,MAAM,IAAI,mBAAmB,CAAC,eAAe,EAAE,yBAAyB,CAAC,CAAC;KAC3E;IAED,MAAM,oBAAoB,GAAG,WAAW,IAAI;QAC1C,GAAG,EAAE;YACH,UAAU,EAAE,IAAI;YAChB,UAAU,EAAE,IAAI;YAChB,UAAU,EAAE,IAAI;SACjB;KACF,CAAC;IACF,MAAM,4BAA4B,GAAG,oBAAoB,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IACvE,OAAO,MAAM,6BAA6B,CAAC,uBAAuB,CAAC,4BAA4B,CAAC,CAAC;AACnG,CAAC;AAED,cAAc;AACd;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,oBAAoB,CAGhD;IACA,aAAa,EAAE,uBAAuB;IACtC,SAAS,EAAE,mBAAmB;CAC/B,CAAC,CAAC","sourcesContent":["import { createPermissionHook, Platform, UnavailabilityError } from 'expo-modules-core';\n\nimport {\n NotificationPermissionsRequest,\n NotificationPermissionsStatus,\n} from './NotificationPermissions.types';\nimport NotificationPermissionsModule from './NotificationPermissionsModule';\n\n/**\n * Calling this function checks current permissions settings related to notifications.\n * It lets you verify whether the app is currently allowed to display alerts, play sounds, etc.\n * There is no user-facing effect of calling this.\n * @return It returns a `Promise` resolving to an object represents permission settings ([`NotificationPermissionsStatus`](#notificationpermissionsstatus)).\n * On iOS, make sure you [properly interpret the permissions response](#interpret-the-ios-permissions-response).\n * @example Check if the app is allowed to send any type of notifications (interrupting and non-interrupting–provisional on iOS).\n * ```ts\n * import * as Notifications from 'expo-notifications';\n *\n * export async function allowsNotificationsAsync() {\n * const settings = await Notifications.getPermissionsAsync();\n * return (\n * settings.granted || settings.ios?.status === Notifications.IosAuthorizationStatus.PROVISIONAL\n * );\n * }\n * ```\n * @header permissions\n */\nexport async function getPermissionsAsync() {\n if (!NotificationPermissionsModule.getPermissionsAsync) {\n throw new UnavailabilityError('Notifications', 'getPermissionsAsync');\n }\n\n return await NotificationPermissionsModule.getPermissionsAsync();\n}\n\n/**\n * Prompts the user for notification permissions according to request. **Request defaults to asking the user to allow displaying alerts,\n * setting badge count and playing sounds**.\n * @param permissions An object representing configuration for the request scope.\n * @return It returns a Promise resolving to an object represents permission settings ([`NotificationPermissionsStatus`](#notificationpermissionsstatus)).\n * On iOS, make sure you [properly interpret the permissions response](#interpret-the-ios-permissions-response).\n * @example Prompts the user to allow the app to show alerts, play sounds, set badge count and let Siri read out messages through AirPods.\n * ```ts\n * import * as Notifications from 'expo-notifications';\n *\n * export function requestPermissionsAsync() {\n * return await Notifications.requestPermissionsAsync({\n * ios: {\n * allowAlert: true,\n * allowBadge: true,\n * allowSound: true,\n * },\n * });\n * }\n * ```\n * @header permissions\n */\nexport async function requestPermissionsAsync(permissions?: NotificationPermissionsRequest) {\n if (!NotificationPermissionsModule.requestPermissionsAsync) {\n throw new UnavailabilityError('Notifications', 'requestPermissionsAsync');\n }\n\n const requestedPermissions = permissions ?? {\n ios: {\n allowAlert: true,\n allowBadge: true,\n allowSound: true,\n },\n };\n const requestedPlatformPermissions = requestedPermissions[Platform.OS];\n return await NotificationPermissionsModule.requestPermissionsAsync(requestedPlatformPermissions);\n}\n\n// @needsAudit\n/**\n * Check or request permissions to send and receive push notifications.\n * This uses both `requestPermissionsAsync` and `getPermissionsAsync` to interact with the permissions.\n * @example\n * ```ts\n * const [permissionResponse, requestPermission] = Notifications.usePermissions();\n * ```\n * @header permission\n */\nexport const usePermissions = createPermissionHook<\n NotificationPermissionsStatus,\n NotificationPermissionsRequest\n>({\n requestMethod: requestPermissionsAsync,\n getMethod: getPermissionsAsync,\n});\n"]}
1
+ {"version":3,"file":"NotificationPermissions.js","sourceRoot":"","sources":["../src/NotificationPermissions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAGlE,OAAO,6BAA6B,MAAM,iCAAiC,CAAC;AAE5E;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB;IACvC,IAAI,CAAC,6BAA6B,CAAC,mBAAmB,EAAE;QACtD,MAAM,IAAI,mBAAmB,CAAC,eAAe,EAAE,qBAAqB,CAAC,CAAC;KACvE;IAED,OAAO,MAAM,6BAA6B,CAAC,mBAAmB,EAAE,CAAC;AACnE,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,WAA4C;IACxF,IAAI,CAAC,6BAA6B,CAAC,uBAAuB,EAAE;QAC1D,MAAM,IAAI,mBAAmB,CAAC,eAAe,EAAE,yBAAyB,CAAC,CAAC;KAC3E;IAED,MAAM,oBAAoB,GAAG,WAAW,IAAI;QAC1C,GAAG,EAAE;YACH,UAAU,EAAE,IAAI;YAChB,UAAU,EAAE,IAAI;YAChB,UAAU,EAAE,IAAI;SACjB;KACF,CAAC;IACF,MAAM,4BAA4B,GAAG,oBAAoB,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IACvE,OAAO,MAAM,6BAA6B,CAAC,uBAAuB,CAAC,4BAA4B,CAAC,CAAC;AACnG,CAAC","sourcesContent":["import { Platform, UnavailabilityError } from 'expo-modules-core';\n\nimport { NotificationPermissionsRequest } from './NotificationPermissions.types';\nimport NotificationPermissionsModule from './NotificationPermissionsModule';\n\n/**\n * Calling this function checks current permissions settings related to notifications.\n * It lets you verify whether the app is currently allowed to display alerts, play sounds, etc.\n * There is no user-facing effect of calling this.\n * @return It returns a `Promise` resolving to an object represents permission settings ([`NotificationPermissionsStatus`](#notificationpermissionsstatus)).\n * On iOS, make sure you [properly interpret the permissions response](#interpret-the-ios-permissions-response).\n * @example Check if the app is allowed to send any type of notifications (interrupting and non-interrupting–provisional on iOS).\n * ```ts\n * import * as Notifications from 'expo-notifications';\n *\n * export async function allowsNotificationsAsync() {\n * const settings = await Notifications.getPermissionsAsync();\n * return (\n * settings.granted || settings.ios?.status === Notifications.IosAuthorizationStatus.PROVISIONAL\n * );\n * }\n * ```\n * @header permissions\n */\nexport async function getPermissionsAsync() {\n if (!NotificationPermissionsModule.getPermissionsAsync) {\n throw new UnavailabilityError('Notifications', 'getPermissionsAsync');\n }\n\n return await NotificationPermissionsModule.getPermissionsAsync();\n}\n\n/**\n * Prompts the user for notification permissions according to request. **Request defaults to asking the user to allow displaying alerts,\n * setting badge count and playing sounds**.\n * @param permissions An object representing configuration for the request scope.\n * @return It returns a Promise resolving to an object represents permission settings ([`NotificationPermissionsStatus`](#notificationpermissionsstatus)).\n * On iOS, make sure you [properly interpret the permissions response](#interpret-the-ios-permissions-response).\n * @example Prompts the user to allow the app to show alerts, play sounds, set badge count and let Siri read out messages through AirPods.\n * ```ts\n * import * as Notifications from 'expo-notifications';\n *\n * export function requestPermissionsAsync() {\n * return await Notifications.requestPermissionsAsync({\n * ios: {\n * allowAlert: true,\n * allowBadge: true,\n * allowSound: true,\n * },\n * });\n * }\n * ```\n * @header permissions\n */\nexport async function requestPermissionsAsync(permissions?: NotificationPermissionsRequest) {\n if (!NotificationPermissionsModule.requestPermissionsAsync) {\n throw new UnavailabilityError('Notifications', 'requestPermissionsAsync');\n }\n\n const requestedPermissions = permissions ?? {\n ios: {\n allowAlert: true,\n allowBadge: true,\n allowSound: true,\n },\n };\n const requestedPlatformPermissions = requestedPermissions[Platform.OS];\n return await NotificationPermissionsModule.requestPermissionsAsync(requestedPlatformPermissions);\n}\n"]}
@@ -1,4 +1,3 @@
1
- import type { PermissionExpiration, PermissionResponse, PermissionStatus, EventSubscription } from 'expo-modules-core';
2
1
  /**
3
2
  * An object represents a notification delivered by a push notification system.
4
3
  *
@@ -702,5 +701,5 @@ export type NotificationCategoryOptions = {
702
701
  */
703
702
  allowAnnouncement?: boolean;
704
703
  };
705
- export type { EventSubscription as Subscription, PermissionResponse, PermissionStatus, PermissionExpiration, };
704
+ export { PermissionExpiration, PermissionResponse, EventSubscription, PermissionStatus, } from 'expo-modules-core';
706
705
  //# sourceMappingURL=Notifications.types.d.ts.map