expo-notifications 0.28.16 → 0.28.18

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 (49) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/android/build.gradle +23 -9
  3. package/android/src/main/java/expo/modules/notifications/Exceptions.kt +0 -13
  4. package/android/src/main/java/expo/modules/notifications/notifications/NotificationSerializer.java +10 -11
  5. package/android/src/main/java/expo/modules/notifications/notifications/SoundResolver.java +1 -1
  6. package/android/src/main/java/expo/modules/notifications/notifications/debug/DebugLogging.kt +3 -4
  7. package/android/src/main/java/expo/modules/notifications/notifications/emitting/NotificationsEmitter.kt +5 -0
  8. package/android/src/main/java/expo/modules/notifications/notifications/handling/NotificationsHandler.kt +1 -2
  9. package/android/src/main/java/expo/modules/notifications/notifications/handling/SingleNotificationHandlerTask.java +2 -2
  10. package/android/src/main/java/expo/modules/notifications/notifications/interfaces/INotificationContent.kt +38 -0
  11. package/android/src/main/java/expo/modules/notifications/notifications/interfaces/NotificationBuilder.kt +34 -0
  12. package/android/src/main/java/expo/modules/notifications/notifications/interfaces/NotificationTrigger.java +0 -3
  13. package/android/src/main/java/expo/modules/notifications/notifications/model/Notification.java +7 -7
  14. package/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java +43 -7
  15. package/android/src/main/java/expo/modules/notifications/notifications/model/NotificationRequest.java +4 -3
  16. package/android/src/main/java/expo/modules/notifications/notifications/model/RemoteNotificationContent.kt +99 -0
  17. package/android/src/main/java/expo/modules/notifications/notifications/model/triggers/FirebaseNotificationTrigger.kt +43 -0
  18. package/android/src/main/java/expo/modules/notifications/notifications/presentation/ExpoNotificationPresentationModule.kt +1 -11
  19. package/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/BaseNotificationBuilder.java +2 -2
  20. package/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/CategoryAwareNotificationBuilder.java +3 -2
  21. package/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ChannelAwareNotificationBuilder.java +6 -7
  22. package/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/DownloadImage.kt +23 -0
  23. package/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.kt +326 -0
  24. package/android/src/main/java/expo/modules/notifications/service/NotificationsService.kt +48 -44
  25. package/android/src/main/java/expo/modules/notifications/service/delegates/ExpoPresentationDelegate.kt +36 -16
  26. package/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt +5 -28
  27. package/build/NotificationsEmitter.d.ts +11 -1
  28. package/build/NotificationsEmitter.d.ts.map +1 -1
  29. package/build/NotificationsEmitter.js +31 -2
  30. package/build/NotificationsEmitter.js.map +1 -1
  31. package/build/useLastNotificationResponse.d.ts.map +1 -1
  32. package/build/useLastNotificationResponse.js +5 -1
  33. package/build/useLastNotificationResponse.js.map +1 -1
  34. package/ios/EXNotifications/Notifications/Emitter/EXNotificationsEmitter.h +1 -0
  35. package/ios/EXNotifications/Notifications/Emitter/EXNotificationsEmitter.m +8 -1
  36. package/package.json +2 -2
  37. package/plugin/build/withNotificationsAndroid.d.ts +5 -5
  38. package/plugin/build/withNotificationsAndroid.js +18 -16
  39. package/plugin/src/withNotificationsAndroid.ts +18 -18
  40. package/src/NotificationsEmitter.ts +33 -2
  41. package/src/useLastNotificationResponse.ts +5 -0
  42. package/android/src/main/java/expo/modules/notifications/notifications/JSONNotificationContentBuilder.java +0 -203
  43. package/android/src/main/java/expo/modules/notifications/notifications/interfaces/NotificationBuilder.java +0 -35
  44. package/android/src/main/java/expo/modules/notifications/notifications/interfaces/NotificationPresentationEffect.java +0 -11
  45. package/android/src/main/java/expo/modules/notifications/notifications/interfaces/NotificationPresentationEffectsManager.java +0 -7
  46. package/android/src/main/java/expo/modules/notifications/notifications/model/triggers/FirebaseNotificationTrigger.java +0 -61
  47. package/android/src/main/java/expo/modules/notifications/notifications/presentation/ExpoNotificationPresentationEffectsManager.java +0 -63
  48. package/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.java +0 -295
  49. package/android/src/main/java/expo/modules/notifications/notifications/presentation/effects/BaseNotificationEffect.java +0 -53
package/CHANGELOG.md CHANGED
@@ -10,6 +10,24 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 0.28.18 — 2024-10-01
14
+
15
+ ### 🎉 New features
16
+
17
+ - Add clearLastNotificationResponseAsync to API. ([#31607](https://github.com/expo/expo/pull/31607) by [@douglowder](https://github.com/douglowder))
18
+
19
+ ### 🐛 Bug fixes
20
+
21
+ - [android] fix notifications actions not being presented ([#31795](https://github.com/expo/expo/pull/31795) by [@vonovak](https://github.com/vonovak))
22
+
23
+ ## 0.28.17 — 2024-09-17
24
+
25
+ ### 🐛 Bug fixes
26
+
27
+ - [Android] image was missing on android when in foreground ([#31405](https://github.com/expo/expo/pull/31405) by [@vonovak](https://github.com/vonovak))
28
+ - [Android] fix local notifications with null trigger. ([#31157](https://github.com/expo/expo/pull/31157) by [@douglowder](https://github.com/douglowder))
29
+ - [Android] Take `channelId` into account when presenting notifications. ([#31201](https://github.com/expo/expo/pull/31201) by [@vonovak](https://github.com/vonovak))
30
+
13
31
  ## 0.28.16 — 2024-08-21
14
32
 
15
33
  ### 🐛 Bug fixes
@@ -1,7 +1,7 @@
1
1
  apply plugin: 'com.android.library'
2
2
 
3
3
  group = 'host.exp.exponent'
4
- version = '0.28.16'
4
+ version = '0.28.18'
5
5
 
6
6
  def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
7
7
  apply from: expoModulesCorePlugin
@@ -14,26 +14,40 @@ android {
14
14
  namespace "expo.modules.notifications"
15
15
  defaultConfig {
16
16
  versionCode 21
17
- versionName '0.28.16'
17
+ versionName '0.28.18'
18
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18
19
  }
19
20
 
20
21
  buildFeatures {
21
22
  buildConfig true
22
23
  }
24
+ testOptions {
25
+ unitTests.all { test ->
26
+ testLogging {
27
+ outputs.upToDateWhen { false }
28
+ events "passed", "failed", "skipped", "standardError"
29
+ showCauses true
30
+ showExceptions true
31
+ showStandardStreams true
32
+ }
33
+ }
34
+ }
23
35
  }
24
36
 
25
37
  dependencies {
26
- api 'androidx.core:core:1.5.0'
27
- api 'androidx.lifecycle:lifecycle-runtime:2.2.0'
28
- api 'androidx.lifecycle:lifecycle-process:2.2.0'
29
- api 'androidx.lifecycle:lifecycle-common-java8:2.2.0'
30
-
38
+ implementation 'androidx.core:core:1.6.0'
39
+ implementation 'androidx.lifecycle:lifecycle-runtime:2.2.0'
40
+ implementation 'androidx.lifecycle:lifecycle-process:2.2.0'
41
+ implementation 'androidx.lifecycle:lifecycle-common-java8:2.2.0'
42
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
31
43
 
32
- api 'com.google.firebase:firebase-messaging:22.0.0'
44
+ // release notes in https://firebase.google.com/support/release-notes/android - cmd + f "Cloud Messaging version"
45
+ implementation 'com.google.firebase:firebase-messaging:24.0.1'
33
46
 
34
- api 'me.leolin:ShortcutBadger:1.1.22@aar'
47
+ implementation 'me.leolin:ShortcutBadger:1.1.22@aar'
35
48
 
36
49
  if (project.findProject(':expo-modules-test-core')) {
37
50
  testImplementation project(':expo-modules-test-core')
51
+ androidTestImplementation project(':expo-modules-test-core')
38
52
  }
39
53
  }
@@ -7,16 +7,3 @@ class ModuleNotFoundException(moduleClass: KClass<*>) :
7
7
  CodedException(message = "$moduleClass module not found")
8
8
 
9
9
  class NotificationWasAlreadyHandledException(val id: String) : CodedException("Failed to handle notification $id, it has already been handled.")
10
-
11
- fun expo.modules.kotlin.Promise.toLegacyPromise(): expo.modules.core.Promise {
12
- val newPromise = this
13
- return object : expo.modules.core.Promise {
14
- override fun resolve(value: Any?) {
15
- newPromise.resolve(value)
16
- }
17
-
18
- override fun reject(c: String?, m: String?, e: Throwable?) {
19
- newPromise.reject(c ?: "unknown", m, e)
20
- }
21
- }
22
- }
@@ -3,7 +3,6 @@ package expo.modules.notifications.notifications;
3
3
  import static expo.modules.notifications.UtilsKt.filteredBundleForJSTypeConverter;
4
4
  import static expo.modules.notifications.UtilsKt.isValidJSONString;
5
5
 
6
- import android.os.Build;
7
6
  import android.os.Bundle;
8
7
 
9
8
  import androidx.annotation.Nullable;
@@ -22,10 +21,10 @@ import java.util.List;
22
21
  import java.util.Map;
23
22
  import java.util.Set;
24
23
 
24
+ import expo.modules.notifications.notifications.interfaces.INotificationContent;
25
25
  import expo.modules.notifications.notifications.interfaces.NotificationTrigger;
26
26
  import expo.modules.notifications.notifications.interfaces.SchedulableNotificationTrigger;
27
27
  import expo.modules.notifications.notifications.model.Notification;
28
- import expo.modules.notifications.notifications.model.NotificationContent;
29
28
  import expo.modules.notifications.notifications.model.NotificationRequest;
30
29
  import expo.modules.notifications.notifications.model.NotificationResponse;
31
30
  import expo.modules.notifications.notifications.model.TextInputNotificationResponse;
@@ -51,7 +50,7 @@ public class NotificationSerializer {
51
50
  public static Bundle toBundle(Notification notification) {
52
51
  Bundle serializedNotification = new Bundle();
53
52
  serializedNotification.putBundle("request", toBundle(notification.getNotificationRequest()));
54
- serializedNotification.putLong("date", notification.getDate().getTime());
53
+ serializedNotification.putLong("date", notification.getOriginDate().getTime());
55
54
  return serializedNotification;
56
55
  }
57
56
 
@@ -76,7 +75,10 @@ public class NotificationSerializer {
76
75
  // and we copy the data as is
77
76
  content.putBundle("data", toBundle(data));
78
77
  }
79
- } else if(request.getTrigger() instanceof SchedulableNotificationTrigger) {
78
+ } else if(
79
+ request.getTrigger() instanceof SchedulableNotificationTrigger ||
80
+ request.getTrigger() == null
81
+ ) {
80
82
  JSONObject body = request.getContent().getBody();
81
83
  if (body != null) {
82
84
  // Expo sends notification.body as data.message, and JSON stringifies data.body
@@ -96,7 +98,7 @@ public class NotificationSerializer {
96
98
  return result;
97
99
  }
98
100
 
99
- public static Bundle toBundle(NotificationContent content) {
101
+ public static Bundle toBundle(INotificationContent content) {
100
102
  Bundle serializedContent = new Bundle();
101
103
  serializedContent.putString("title", content.getTitle());
102
104
  serializedContent.putString("subtitle", content.getSubtitle());
@@ -110,9 +112,9 @@ public class NotificationSerializer {
110
112
  } else {
111
113
  serializedContent.putString("badge", null);
112
114
  }
113
- if (content.shouldPlayDefaultSound()) {
115
+ if (content.getShouldPlayDefaultSound()) {
114
116
  serializedContent.putString("sound", "default");
115
- } else if (content.getSound() != null) {
117
+ } else if (content.getSoundName() != null) {
116
118
  serializedContent.putString("sound", "custom");
117
119
  } else {
118
120
  serializedContent.putString("sound", null);
@@ -225,10 +227,7 @@ public class NotificationSerializer {
225
227
 
226
228
  @Nullable
227
229
  private static String getChannelId(NotificationTrigger trigger) {
228
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
229
- return trigger.getNotificationChannel();
230
- }
231
- return null;
230
+ return trigger.getNotificationChannel();
232
231
  }
233
232
 
234
233
  @NotNull
@@ -9,7 +9,7 @@ import androidx.annotation.Nullable;
9
9
 
10
10
  /**
11
11
  * A shared logic between ContentBuilders ({@link ArgumentsNotificationContentBuilder}
12
- * and {@link JSONNotificationContentBuilder}) for resolving sounds based on the "sound" property.
12
+ * and {@link RemoteNotificationContent}) for resolving sounds based on the "soundName" property.
13
13
  */
14
14
  public class SoundResolver {
15
15
  private Context mContext;
@@ -61,7 +61,6 @@ object DebugLogging {
61
61
  Log.i("expo-notifications", logMessage)
62
62
  }
63
63
 
64
- @RequiresApi(Build.VERSION_CODES.O)
65
64
  fun logNotification(caller: String, notification: Notification) {
66
65
  if (!BuildConfig.DEBUG) {
67
66
  // Do not log for release/production builds
@@ -73,11 +72,11 @@ object DebugLogging {
73
72
  notification.notificationRequest.content.title: ${notification.notificationRequest.content.title}
74
73
  notification.notificationRequest.content.subtitle: ${notification.notificationRequest.content.subtitle}
75
74
  notification.notificationRequest.content.text: ${notification.notificationRequest.content.text}
76
- notification.notificationRequest.content.sound: ${notification.notificationRequest.content.sound}
75
+ notification.notificationRequest.content.sound: ${notification.notificationRequest.content.soundName}
76
+ notification.notificationRequest.content.channelID: ${notification.notificationRequest.trigger.notificationChannel}
77
77
  notification.notificationRequest.content.body: ${notification.notificationRequest.content.body}
78
78
  notification.notificationRequest.content.color: ${notification.notificationRequest.content.color}
79
- notification.notificationRequest.content.vibrationPattern: ${notification.notificationRequest.content.vibrationPattern.contentToString()}
80
- notification.notificationRequest.trigger.notificationChannel: ${notification.notificationRequest.trigger.notificationChannel}
79
+ notification.notificationRequest.content.vibrationPattern: ${notification.notificationRequest.content.vibrationPattern?.contentToString()}
81
80
  notification.notificationRequest.identifier: ${notification.notificationRequest.identifier}
82
81
  """.trimIndent()
83
82
 
@@ -41,6 +41,11 @@ open class NotificationsEmitter : Module(), NotificationListener {
41
41
  AsyncFunction<Bundle?>("getLastNotificationResponseAsync") {
42
42
  lastNotificationResponseBundle
43
43
  }
44
+
45
+ AsyncFunction("clearLastNotificationResponseAsync") {
46
+ lastNotificationResponseBundle = null
47
+ null
48
+ }
44
49
  }
45
50
 
46
51
  /**
@@ -13,7 +13,6 @@ import expo.modules.notifications.notifications.interfaces.NotificationListener
13
13
  import expo.modules.notifications.notifications.interfaces.NotificationManager
14
14
  import expo.modules.notifications.notifications.model.Notification
15
15
  import expo.modules.notifications.notifications.model.NotificationBehavior
16
- import expo.modules.notifications.toLegacyPromise
17
16
 
18
17
  class NotificationBehaviourRecord : Record {
19
18
  @Field
@@ -102,7 +101,7 @@ open class NotificationsHandler : Module(), NotificationListener {
102
101
  with(behavior) {
103
102
  task.handleResponse(
104
103
  NotificationBehavior(shouldShowAlert, shouldPlaySound, shouldSetBadge, priority),
105
- promise.toLegacyPromise()
104
+ promise
106
105
  )
107
106
  }
108
107
  }
@@ -5,7 +5,7 @@ import android.os.Bundle;
5
5
  import android.os.Handler;
6
6
  import android.os.ResultReceiver;
7
7
 
8
- import expo.modules.core.Promise;
8
+ import expo.modules.kotlin.Promise;
9
9
  import expo.modules.core.interfaces.services.EventEmitter;
10
10
  import expo.modules.notifications.notifications.NotificationSerializer;
11
11
  import expo.modules.notifications.notifications.model.Notification;
@@ -98,7 +98,7 @@ public class SingleNotificationHandlerTask {
98
98
  protected void onReceiveResult(int resultCode, Bundle resultData) {
99
99
  super.onReceiveResult(resultCode, resultData);
100
100
  if (resultCode == NotificationsService.SUCCESS_CODE) {
101
- promise.resolve(null);
101
+ promise.resolve();
102
102
  } else {
103
103
  Exception e = (Exception) resultData.getSerializable(NotificationsService.EXCEPTION_KEY);
104
104
  promise.reject("ERR_NOTIFICATION_PRESENTATION_FAILED", "Notification presentation failed.", e);
@@ -0,0 +1,38 @@
1
+ package expo.modules.notifications.notifications.interfaces
2
+
3
+ import android.content.Context
4
+ import android.graphics.Bitmap
5
+ import android.os.Parcelable
6
+ import expo.modules.notifications.notifications.enums.NotificationPriority
7
+ import org.json.JSONObject
8
+
9
+ /**
10
+ * This interface is implemented by classes representing notification content.
11
+ * I.e. local notifications [NotificationContent] and remote notifications [RemoteNotificationContent].
12
+ *
13
+ * The reason the two classes exist is that one is persisted locally in SharedPreferences, and the other is not.
14
+ * The first is therefore a bit "fragile" and harder to refactor, while the second is easier to change.
15
+ * This interface exists to provide a common API for both classes.
16
+ * */
17
+ interface INotificationContent : Parcelable {
18
+ val title: String?
19
+ val text: String?
20
+ val subtitle: String?
21
+ val badgeCount: Number?
22
+ val shouldPlayDefaultSound: Boolean
23
+
24
+ // soundName is better off as a string (was an Uri) because in RemoteNotification we can obtain the sound name
25
+ // in local notification we store the uri and derive the sound name from it
26
+ val soundName: String?
27
+ val shouldUseDefaultVibrationPattern: Boolean
28
+ val vibrationPattern: LongArray?
29
+ val body: JSONObject?
30
+ val priority: NotificationPriority
31
+ val color: Number?
32
+ val isAutoDismiss: Boolean
33
+ val categoryId: String?
34
+ val isSticky: Boolean
35
+
36
+ fun containsImage(): Boolean
37
+ suspend fun getImage(context: Context): Bitmap?
38
+ }
@@ -0,0 +1,34 @@
1
+ package expo.modules.notifications.notifications.interfaces
2
+
3
+ import expo.modules.notifications.notifications.model.Notification
4
+ import expo.modules.notifications.notifications.model.NotificationBehavior
5
+
6
+ /**
7
+ * An object capable of building a [Notification] based
8
+ * on a [NotificationContent] spec.
9
+ */
10
+ interface NotificationBuilder {
11
+ /**
12
+ * Pass in a [Notification] based on which the Android notification should be based.
13
+ *
14
+ * @param notification [Notification] on which the notification should be based.
15
+ * @return The same instance of [NotificationBuilder] updated with the notification.
16
+ */
17
+ fun setNotification(notification: Notification?): NotificationBuilder?
18
+
19
+ /**
20
+ * Pass in a [NotificationBehavior] if you want to override the behavior
21
+ * of the notification, i.e. whether it should show a heads-up alert, set badge, etc.
22
+ *
23
+ * @param behavior [NotificationBehavior] to which the presentation effect should conform.
24
+ * @return The same instance of [NotificationBuilder] updated with the remote message.
25
+ */
26
+ fun setAllowedBehavior(behavior: NotificationBehavior?): NotificationBuilder?
27
+
28
+ /**
29
+ * Builds the Android notification based on passed in data.
30
+ *
31
+ * @return Built notification.
32
+ */
33
+ suspend fun build(): android.app.Notification
34
+ }
@@ -1,10 +1,8 @@
1
1
  package expo.modules.notifications.notifications.interfaces;
2
2
 
3
- import android.os.Build;
4
3
  import android.os.Parcelable;
5
4
 
6
5
  import androidx.annotation.Nullable;
7
- import androidx.annotation.RequiresApi;
8
6
 
9
7
  /**
10
8
  * An interface specifying source of the notification, to be implemented
@@ -12,7 +10,6 @@ import androidx.annotation.RequiresApi;
12
10
  */
13
11
  public interface NotificationTrigger extends Parcelable {
14
12
  @Nullable
15
- @RequiresApi(api = Build.VERSION_CODES.O)
16
13
  default String getNotificationChannel() {
17
14
  return null;
18
15
  }
@@ -6,11 +6,11 @@ import android.os.Parcelable;
6
6
  import java.util.Date;
7
7
 
8
8
  /**
9
- * A class representing a single notification received at a particular moment ({@link #mDate}).
9
+ * A class representing a single notification. Origin date ({@link #mOriginDate}) is time when it was sent (remote) or when it was posted (local).
10
10
  */
11
11
  public class Notification implements Parcelable {
12
12
  private NotificationRequest mRequest;
13
- private Date mDate;
13
+ private Date mOriginDate;
14
14
 
15
15
  public Notification(NotificationRequest request) {
16
16
  this(request, new Date());
@@ -18,16 +18,16 @@ public class Notification implements Parcelable {
18
18
 
19
19
  public Notification(NotificationRequest request, Date date) {
20
20
  mRequest = request;
21
- mDate = date;
21
+ mOriginDate = date;
22
22
  }
23
23
 
24
24
  protected Notification(Parcel in) {
25
25
  mRequest = in.readParcelable(getClass().getClassLoader());
26
- mDate = new Date(in.readLong());
26
+ mOriginDate = new Date(in.readLong());
27
27
  }
28
28
 
29
- public Date getDate() {
30
- return mDate;
29
+ public Date getOriginDate() {
30
+ return mOriginDate;
31
31
  }
32
32
 
33
33
  public NotificationRequest getNotificationRequest() {
@@ -54,6 +54,6 @@ public class Notification implements Parcelable {
54
54
  @Override
55
55
  public void writeToParcel(Parcel dest, int flags) {
56
56
  dest.writeParcelable(mRequest, 0);
57
- dest.writeLong(mDate.getTime());
57
+ dest.writeLong(mOriginDate.getTime());
58
58
  }
59
59
  }
@@ -1,8 +1,16 @@
1
1
  package expo.modules.notifications.notifications.model;
2
2
 
3
+ import static expo.modules.notifications.notifications.presentation.builders.ExpoNotificationBuilder.META_DATA_LARGE_ICON_KEY;
4
+
5
+ import android.content.Context;
6
+ import android.content.pm.ApplicationInfo;
7
+ import android.content.pm.PackageManager;
8
+ import android.graphics.Bitmap;
9
+ import android.graphics.BitmapFactory;
3
10
  import android.net.Uri;
4
11
  import android.os.Parcel;
5
12
  import android.os.Parcelable;
13
+ import android.util.Log;
6
14
 
7
15
  import org.json.JSONException;
8
16
  import org.json.JSONObject;
@@ -11,15 +19,21 @@ import java.io.IOException;
11
19
  import java.io.ObjectStreamException;
12
20
  import java.io.Serializable;
13
21
 
22
+ import androidx.annotation.NonNull;
14
23
  import androidx.annotation.Nullable;
15
24
 
16
25
  import expo.modules.notifications.notifications.enums.NotificationPriority;
26
+ import expo.modules.notifications.notifications.interfaces.INotificationContent;
27
+ import kotlin.coroutines.Continuation;
17
28
 
18
29
  /**
19
- * A POJO representing a notification content: title, message, body, etc. Instances
30
+ * A POJO representing a local notification content: title, message, body, etc. Instances
20
31
  * should be created using {@link NotificationContent.Builder}.
32
+ *
33
+ * Note that it implements {@link Serializable} interfaces to store the object in the SharedPreferences.
34
+ * Refactoring this class may require a migration strategy for the data stored in SharedPreferences.
21
35
  */
22
- public class NotificationContent implements Parcelable, Serializable {
36
+ public class NotificationContent implements Parcelable, Serializable, INotificationContent {
23
37
  private String mTitle;
24
38
  private String mText;
25
39
  private String mSubtitle;
@@ -70,17 +84,39 @@ public class NotificationContent implements Parcelable, Serializable {
70
84
  return mBadgeCount;
71
85
  }
72
86
 
73
- public boolean shouldPlayDefaultSound() {
87
+ @Override
88
+ public boolean getShouldPlayDefaultSound() {
74
89
  return mShouldPlayDefaultSound;
75
90
  }
76
91
 
92
+ @Override
93
+ public boolean getShouldUseDefaultVibrationPattern() {
94
+ return mShouldUseDefaultVibrationPattern;
95
+ }
96
+
77
97
  @Nullable
78
- public Uri getSound() {
79
- return mSound;
98
+ public String getSoundName() {
99
+ return mSound != null ? mSound.getLastPathSegment() : null;
80
100
  }
81
101
 
82
- public boolean shouldUseDefaultVibrationPattern() {
83
- return mShouldUseDefaultVibrationPattern;
102
+ @Nullable
103
+ @Override
104
+ public Object getImage(@NonNull Context context, @NonNull Continuation<? super Bitmap> $completion) {
105
+ try {
106
+ ApplicationInfo ai = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
107
+ if (ai.metaData.containsKey(META_DATA_LARGE_ICON_KEY)) {
108
+ int resourceId = ai.metaData.getInt(META_DATA_LARGE_ICON_KEY);
109
+ return BitmapFactory.decodeResource(context.getResources(), resourceId);
110
+ }
111
+ } catch (PackageManager.NameNotFoundException | ClassCastException e) {
112
+ Log.e("expo-notifications", "Could not have fetched large notification icon.", e);
113
+ }
114
+ return null;
115
+ }
116
+
117
+ @Override
118
+ public boolean containsImage() {
119
+ return true;
84
120
  }
85
121
 
86
122
  @Nullable
@@ -5,6 +5,7 @@ import android.os.Parcelable;
5
5
 
6
6
  import java.io.Serializable;
7
7
 
8
+ import expo.modules.notifications.notifications.interfaces.INotificationContent;
8
9
  import expo.modules.notifications.notifications.interfaces.NotificationTrigger;
9
10
 
10
11
  /**
@@ -13,16 +14,16 @@ import expo.modules.notifications.notifications.interfaces.NotificationTrigger;
13
14
  */
14
15
  public class NotificationRequest implements Parcelable, Serializable {
15
16
  private String mIdentifier;
16
- private NotificationContent mContent;
17
+ private INotificationContent mContent;
17
18
  private NotificationTrigger mTrigger;
18
19
 
19
- public NotificationRequest(String identifier, NotificationContent content, NotificationTrigger trigger) {
20
+ public NotificationRequest(String identifier, INotificationContent content, NotificationTrigger trigger) {
20
21
  mIdentifier = identifier;
21
22
  mContent = content;
22
23
  mTrigger = trigger;
23
24
  }
24
25
 
25
- public NotificationContent getContent() {
26
+ public INotificationContent getContent() {
26
27
  return mContent;
27
28
  }
28
29
 
@@ -0,0 +1,99 @@
1
+ package expo.modules.notifications.notifications.model
2
+
3
+ import android.content.Context
4
+ import android.graphics.Bitmap
5
+ import android.os.Parcel
6
+ import android.os.Parcelable
7
+ import com.google.firebase.messaging.RemoteMessage
8
+ import expo.modules.notifications.notifications.enums.NotificationPriority
9
+ import expo.modules.notifications.notifications.interfaces.INotificationContent
10
+ import expo.modules.notifications.notifications.presentation.builders.downloadImage
11
+ import org.json.JSONObject
12
+
13
+ /**
14
+ * A POJO representing a remote notification content: title, message, body, etc.
15
+ * The content originates in a RemoteMessage object.
16
+ *
17
+ * Instances of this class are not persisted in SharedPreferences (unlike {@link NotificationContent}. This class does
18
+ * not implement Serializable, but Parcelable ensures we can pass instances between different parts of the application.
19
+ */
20
+ class RemoteNotificationContent(private val remoteMessage: RemoteMessage) : INotificationContent {
21
+
22
+ constructor(parcel: Parcel) : this(parcel.readParcelable<RemoteMessage>(RemoteMessage::class.java.classLoader)!!)
23
+
24
+ override suspend fun getImage(context: Context): Bitmap? {
25
+ val uri = remoteMessage.notification?.imageUrl
26
+ return uri?.let { downloadImage(it) }
27
+ }
28
+
29
+ override fun containsImage(): Boolean {
30
+ return remoteMessage.notification?.imageUrl != null
31
+ }
32
+
33
+ override val title: String?
34
+ get() = remoteMessage.notification?.title
35
+
36
+ override val text: String?
37
+ get() = remoteMessage.notification?.body
38
+
39
+ override val shouldPlayDefaultSound: Boolean
40
+ get() = remoteMessage.notification?.sound == null
41
+
42
+ override val soundName: String?
43
+ get() = remoteMessage.notification?.sound
44
+
45
+ override val shouldUseDefaultVibrationPattern: Boolean
46
+ get() = remoteMessage.notification?.defaultVibrateSettings == true
47
+
48
+ override val vibrationPattern: LongArray?
49
+ get() = remoteMessage.notification?.vibrateTimings
50
+
51
+ override val body: JSONObject?
52
+ get() = try {
53
+ remoteMessage.data["body"]?.let { JSONObject(it) }
54
+ } catch (e: Exception) {
55
+ null
56
+ }
57
+
58
+ override val priority: NotificationPriority
59
+ get() = when (remoteMessage.priority) {
60
+ RemoteMessage.PRIORITY_HIGH -> NotificationPriority.HIGH
61
+ else -> NotificationPriority.DEFAULT
62
+ }
63
+
64
+ override val color: Number?
65
+ get() = remoteMessage.notification?.color?.let { android.graphics.Color.parseColor(it) }
66
+
67
+ // NOTE the following getter functions are here because the local notification content class has them
68
+ // and this class conforms to the same interface. They are not supported by FCM.
69
+ override val isAutoDismiss: Boolean
70
+ get() = remoteMessage.data["autoDismiss"]?.toBoolean() ?: true
71
+
72
+ override val categoryId: String?
73
+ get() = remoteMessage.data["categoryId"]
74
+
75
+ override val isSticky: Boolean
76
+ get() = remoteMessage.data["sticky"]?.toBoolean() ?: false
77
+
78
+ override val subtitle: String?
79
+ get() = remoteMessage.data["subtitle"]
80
+
81
+ override val badgeCount: Number?
82
+ get() = remoteMessage.data["badge"]?.toIntOrNull()
83
+
84
+ override fun describeContents(): Int = 0
85
+
86
+ override fun writeToParcel(dest: Parcel, flags: Int) {
87
+ dest.writeParcelable(remoteMessage, flags)
88
+ }
89
+
90
+ companion object CREATOR : Parcelable.Creator<RemoteNotificationContent> {
91
+ override fun createFromParcel(parcel: Parcel): RemoteNotificationContent {
92
+ return RemoteNotificationContent(parcel)
93
+ }
94
+
95
+ override fun newArray(size: Int): Array<RemoteNotificationContent?> {
96
+ return arrayOfNulls(size)
97
+ }
98
+ }
99
+ }
@@ -0,0 +1,43 @@
1
+ package expo.modules.notifications.notifications.model.triggers
2
+
3
+ import android.os.Build
4
+ import android.os.Parcel
5
+ import android.os.Parcelable
6
+ import androidx.annotation.RequiresApi
7
+ import com.google.firebase.messaging.RemoteMessage
8
+ import expo.modules.notifications.notifications.interfaces.NotificationTrigger
9
+
10
+ /**
11
+ * A trigger representing an incoming remote Firebase notification.
12
+ */
13
+ class FirebaseNotificationTrigger(private val remoteMessage: RemoteMessage) : NotificationTrigger {
14
+
15
+ private constructor(parcel: Parcel) : this(
16
+ parcel.readParcelable(FirebaseNotificationTrigger::class.java.classLoader)
17
+ ?: throw IllegalArgumentException("RemoteMessage from readParcelable must not be null")
18
+ )
19
+
20
+ fun getRemoteMessage(): RemoteMessage = remoteMessage
21
+
22
+ @RequiresApi(api = Build.VERSION_CODES.O)
23
+ override fun getNotificationChannel(): String? {
24
+ val channelId = remoteMessage.notification?.channelId ?: remoteMessage.data["channelId"]
25
+ return channelId ?: super.getNotificationChannel()
26
+ }
27
+
28
+ override fun describeContents(): Int {
29
+ return 0
30
+ }
31
+
32
+ override fun writeToParcel(dest: Parcel, flags: Int) {
33
+ dest.writeParcelable(remoteMessage, 0)
34
+ }
35
+
36
+ companion object {
37
+ @JvmField
38
+ val CREATOR = object : Parcelable.Creator<FirebaseNotificationTrigger> {
39
+ override fun createFromParcel(parcel: Parcel) = FirebaseNotificationTrigger(parcel)
40
+ override fun newArray(size: Int) = arrayOfNulls<FirebaseNotificationTrigger>(size)
41
+ }
42
+ }
43
+ }