From cd1916b776a80b60a32b566ffaa0084bcf154cc8 Mon Sep 17 00:00:00 2001 From: Vojtech Novak Date: Thu, 19 Feb 2026 07:35:01 +0100 Subject: [PATCH 1/7] [notifications] migrate several Java files to Kotlin (#43249) --- packages/expo-notifications/CHANGELOG.md | 2 + ...kgroundRemoteNotificationTaskConsumer.java | 130 ------------------ ...ackgroundRemoteNotificationTaskConsumer.kt | 94 +++++++++++++ ...ExpoNotificationsCategoriesSerializer.java | 7 +- .../NotificationsCategoriesSerializer.java | 11 -- .../NotificationsCategoriesSerializer.kt | 8 ++ .../InvalidVibrationPatternException.java | 14 -- .../InvalidVibrationPatternException.kt | 9 ++ .../NotificationChannelGroupManagerModule.kt | 3 +- .../NotificationChannelManagerModule.kt | 3 +- ...poNotificationsChannelGroupSerializer.java | 7 +- .../ExpoNotificationsChannelSerializer.java | 8 +- .../NotificationsChannelGroupSerializer.java | 20 --- .../NotificationsChannelGroupSerializer.kt | 19 +++ .../NotificationsChannelSerializer.java | 35 ----- .../NotificationsChannelSerializer.kt | 34 +++++ .../emitting/NotificationsEmitter.kt | 2 +- .../interfaces/NotificationListener.java | 47 ------- .../interfaces/NotificationListener.kt | 26 ++++ .../SchedulableNotificationTrigger.java | 20 --- .../SchedulableNotificationTrigger.kt | 17 +++ 21 files changed, 218 insertions(+), 298 deletions(-) delete mode 100644 packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/background/BackgroundRemoteNotificationTaskConsumer.java create mode 100644 packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/background/BackgroundRemoteNotificationTaskConsumer.kt delete mode 100644 packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/categories/serializers/NotificationsCategoriesSerializer.java create mode 100644 packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/categories/serializers/NotificationsCategoriesSerializer.kt delete mode 100644 packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/InvalidVibrationPatternException.java create mode 100644 packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/InvalidVibrationPatternException.kt delete mode 100644 packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/serializers/NotificationsChannelGroupSerializer.java create mode 100644 packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/serializers/NotificationsChannelGroupSerializer.kt delete mode 100644 packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/serializers/NotificationsChannelSerializer.java create mode 100644 packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/serializers/NotificationsChannelSerializer.kt delete mode 100644 packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/NotificationListener.java create mode 100644 packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/NotificationListener.kt delete mode 100644 packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/SchedulableNotificationTrigger.java create mode 100644 packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/SchedulableNotificationTrigger.kt diff --git a/packages/expo-notifications/CHANGELOG.md b/packages/expo-notifications/CHANGELOG.md index bd1b31ef22d7fb..61b4c52e3c4322 100644 --- a/packages/expo-notifications/CHANGELOG.md +++ b/packages/expo-notifications/CHANGELOG.md @@ -17,6 +17,8 @@ ### 💡 Others +- [Android] migrate several Java files to Kotlin ([#43249](https://github.com/expo/expo/pull/43249) by [@vonovak](https://github.com/vonovak)) + ## 55.0.7 — 2026-02-16 ### 💡 Others diff --git a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/background/BackgroundRemoteNotificationTaskConsumer.java b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/background/BackgroundRemoteNotificationTaskConsumer.java deleted file mode 100644 index 89d1b3f538d618..00000000000000 --- a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/background/BackgroundRemoteNotificationTaskConsumer.java +++ /dev/null @@ -1,130 +0,0 @@ -package expo.modules.notifications.notifications.background; - -import android.app.job.JobParameters; -import android.app.job.JobService; -import android.content.Context; -import android.os.Bundle; -import android.os.PersistableBundle; -import android.util.Log; -import androidx.annotation.NonNull; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import expo.modules.notifications.notifications.NotificationSerializer; -import expo.modules.notifications.service.delegates.FirebaseMessagingDelegate; -import expo.modules.interfaces.taskManager.TaskConsumer; -import expo.modules.interfaces.taskManager.TaskConsumerInterface; -import expo.modules.interfaces.taskManager.TaskExecutionCallback; -import expo.modules.interfaces.taskManager.TaskInterface; -import expo.modules.interfaces.taskManager.TaskManagerUtilsInterface; - -/** - * Represents a task to be run when the app is receives a remote push - * notification. Map of current tasks is maintained in {@link FirebaseMessagingDelegate}. - * - * Instances are instantiated by expo task manager, after being registered in ExpoBackgroundNotificationTasksModule - */ -public class BackgroundRemoteNotificationTaskConsumer extends TaskConsumer implements TaskConsumerInterface { - private static final String NOTIFICATION_KEY = "notification"; - - private TaskInterface mTask; - - public BackgroundRemoteNotificationTaskConsumer(Context context, TaskManagerUtilsInterface taskManagerUtils) { - super(context, taskManagerUtils); - FirebaseMessagingDelegate.Companion.addBackgroundTaskConsumer(this); - } - - //region TaskConsumerInterface - - @Override - public String taskType() { - return "remote-notification"; - } - - @Override - public void didRegister(TaskInterface task) { - mTask = task; - } - - @Override - public void didUnregister() { - FirebaseMessagingDelegate.Companion.removeBackgroundTaskConsumer(this); - mTask = null; - } - - public void scheduleJob(Bundle bundle) { - Context context = getContext(); - - if (context != null && mTask != null) { - PersistableBundle data = new PersistableBundle(); - // Bundles are not persistable, so let's convert to a JSON string - data.putString(NOTIFICATION_KEY, bundleToJson(bundle).toString()); - getTaskManagerUtils().scheduleJob(context, mTask, Collections.singletonList(data)); - } - } - - @Override - public boolean didExecuteJob(final JobService jobService, final JobParameters params) { - if (mTask == null) { - return false; - } - - List data = getTaskManagerUtils().extractDataFromJobParams(params); - - for (PersistableBundle item : data) { - Bundle bundle = new Bundle(); - bundle.putBundle(NOTIFICATION_KEY, jsonStringToBundle(item.getString(NOTIFICATION_KEY))); - mTask.execute(bundle, null, new TaskExecutionCallback() { - @Override - public void onFinished(Map response) { - jobService.jobFinished(params, false); - } - }); - } - - // Returning `true` indicates that the job is still running, but in async mode. - // In that case we're obligated to call `jobService.jobFinished` as soon as the async block finishes. - return true; - } - - //endregion - //region private methods - - private static JSONObject bundleToJson(Bundle bundle) { - JSONObject json = new JSONObject(); - for (String key : bundle.keySet()) { - try { - if (bundle.get(key) instanceof Bundle) { - json.put(key, bundleToJson((Bundle) bundle.get(key))); - } else { - json.put(key, JSONObject.wrap(bundle.get(key))); - } - } catch(JSONException e) { - Log.e("expo-notifications", "Could not create JSON object from notification bundle. " + e.getMessage()); - } - } - return json; - } - - private static Bundle jsonStringToBundle(String jsonString) { - Bundle bundle = new Bundle(); - try { - JSONObject jsonObject = new JSONObject(jsonString); - bundle = NotificationSerializer.toBundle(jsonObject); - } catch (JSONException e) { - Log.e("expo-notifications", "Could not parse notification from JSON string. " + e.getMessage()); - } - return bundle; - } - - public void executeTask(@NonNull Bundle bundle) { - mTask.execute(bundle, null); - } - - //endregion -} diff --git a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/background/BackgroundRemoteNotificationTaskConsumer.kt b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/background/BackgroundRemoteNotificationTaskConsumer.kt new file mode 100644 index 00000000000000..6fdc1c63a49dd1 --- /dev/null +++ b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/background/BackgroundRemoteNotificationTaskConsumer.kt @@ -0,0 +1,94 @@ +package expo.modules.notifications.notifications.background + +import android.app.job.JobParameters +import android.app.job.JobService +import android.content.Context +import android.os.Bundle +import android.util.Log +import expo.modules.interfaces.taskManager.TaskConsumer +import expo.modules.interfaces.taskManager.TaskConsumerInterface +import expo.modules.interfaces.taskManager.TaskInterface +import expo.modules.interfaces.taskManager.TaskManagerUtilsInterface +import expo.modules.notifications.notifications.NotificationSerializer +import expo.modules.notifications.service.delegates.FirebaseMessagingDelegate.Companion.addBackgroundTaskConsumer +import expo.modules.notifications.service.delegates.FirebaseMessagingDelegate.Companion.removeBackgroundTaskConsumer +import org.json.JSONException +import org.json.JSONObject + +/** + * Represents a task to be run when the app receives a remote push + * notification. Set of current tasks is maintained in [FirebaseMessagingDelegate]. + * + * Instances are instantiated by expo task manager, after being registered in ExpoBackgroundNotificationTasksModule + */ +class BackgroundRemoteNotificationTaskConsumer( + context: Context?, + taskManagerUtils: TaskManagerUtilsInterface? +) : TaskConsumer(context, taskManagerUtils), TaskConsumerInterface { + + private var task: TaskInterface? = null + + init { + addBackgroundTaskConsumer(this) + } + + override fun taskType() = "remote-notification" + + override fun didRegister(task: TaskInterface?) { + this.task = task + } + + override fun didUnregister() { + removeBackgroundTaskConsumer(this) + task = null + } + + override fun didExecuteJob(jobService: JobService, params: JobParameters?): Boolean { + val task = task ?: return false + + for (item in taskManagerUtils.extractDataFromJobParams(params)) { + val notificationEntry = requireNotNull(item.getString(NOTIFICATION_KEY)) { "Job data missing '$NOTIFICATION_KEY' entry" } + val bundle = Bundle().apply { + putBundle(NOTIFICATION_KEY, jsonStringToBundle(notificationEntry)) + } + task.execute(bundle, null) { jobService.jobFinished(params, false) } + } + + // Returning `true` indicates that the job is still running, but in async mode. + // In that case we're obligated to call `jobService.jobFinished` as soon as the async block finishes. + return true + } + + fun executeTask(bundle: Bundle) { + requireNotNull(task) { "executeTask called but no task is registered" }.execute(bundle, null) + } + + companion object { + private const val NOTIFICATION_KEY = "notification" + + private fun bundleToJson(bundle: Bundle): JSONObject { + val json = JSONObject() + for (key in bundle.keySet()) { + try { + val value = bundle.get(key) + if (value is Bundle) { + json.put(key, bundleToJson(value)) + } else { + json.put(key, JSONObject.wrap(value)) + } + } catch (e: JSONException) { + Log.e("expo-notifications", "Could not create JSON object from notification bundle. ${e.message}") + } + } + return json + } + + private fun jsonStringToBundle(jsonString: String): Bundle? = + try { + NotificationSerializer.toBundle(JSONObject(jsonString)) + } catch (e: JSONException) { + Log.e("expo-notifications", "Could not parse notification from JSON string. ${e.message}") + null + } + } +} diff --git a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/categories/serializers/ExpoNotificationsCategoriesSerializer.java b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/categories/serializers/ExpoNotificationsCategoriesSerializer.java index 1c8cf5166c0afd..8d723a935f428f 100644 --- a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/categories/serializers/ExpoNotificationsCategoriesSerializer.java +++ b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/categories/serializers/ExpoNotificationsCategoriesSerializer.java @@ -12,12 +12,9 @@ import expo.modules.notifications.notifications.model.TextInputNotificationAction; public class ExpoNotificationsCategoriesSerializer implements NotificationsCategoriesSerializer { - @Nullable + @NonNull @Override - public Bundle toBundle(@Nullable NotificationCategory category) { - if (category == null) { - return null; - } + public Bundle toBundle(@NonNull NotificationCategory category) { Bundle serializedCategory = new Bundle(); serializedCategory.putString("identifier", getIdentifier(category)); serializedCategory.putParcelableArrayList("actions", toBundleList(category.getActions())); diff --git a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/categories/serializers/NotificationsCategoriesSerializer.java b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/categories/serializers/NotificationsCategoriesSerializer.java deleted file mode 100644 index a00084208e38ad..00000000000000 --- a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/categories/serializers/NotificationsCategoriesSerializer.java +++ /dev/null @@ -1,11 +0,0 @@ -package expo.modules.notifications.notifications.categories.serializers; - -import android.os.Bundle; - -import androidx.annotation.Nullable; -import expo.modules.notifications.notifications.model.NotificationCategory; - -public interface NotificationsCategoriesSerializer { - @Nullable - Bundle toBundle(@Nullable NotificationCategory category); -} diff --git a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/categories/serializers/NotificationsCategoriesSerializer.kt b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/categories/serializers/NotificationsCategoriesSerializer.kt new file mode 100644 index 00000000000000..ec240c453967bd --- /dev/null +++ b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/categories/serializers/NotificationsCategoriesSerializer.kt @@ -0,0 +1,8 @@ +package expo.modules.notifications.notifications.categories.serializers + +import android.os.Bundle +import expo.modules.notifications.notifications.model.NotificationCategory + +interface NotificationsCategoriesSerializer { + fun toBundle(category: NotificationCategory): Bundle +} diff --git a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/InvalidVibrationPatternException.java b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/InvalidVibrationPatternException.java deleted file mode 100644 index bc1b967153ba8d..00000000000000 --- a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/InvalidVibrationPatternException.java +++ /dev/null @@ -1,14 +0,0 @@ -package expo.modules.notifications.notifications.channels; - -import expo.modules.core.errors.CodedRuntimeException; - -public class InvalidVibrationPatternException extends CodedRuntimeException { - public InvalidVibrationPatternException(int invalidValueKey, Object invalidValue) { - super("Invalid value in vibration pattern, expected all elements to be numbers, got: " + invalidValue + " under " + invalidValueKey); - } - - @Override - public String getCode() { - return "ERR_INVALID_VIBRATION_PATTERN"; - } -} diff --git a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/InvalidVibrationPatternException.kt b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/InvalidVibrationPatternException.kt new file mode 100644 index 00000000000000..442e0d8c480e0e --- /dev/null +++ b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/InvalidVibrationPatternException.kt @@ -0,0 +1,9 @@ +package expo.modules.notifications.notifications.channels + +import expo.modules.core.errors.CodedRuntimeException + +class InvalidVibrationPatternException(invalidValueKey: Int, invalidValue: Any?) : + CodedRuntimeException("Invalid value in vibration pattern, expected all elements to be numbers, got: $invalidValue under $invalidValueKey") { + + override fun getCode() = "ERR_INVALID_VIBRATION_PATTERN" +} diff --git a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/NotificationChannelGroupManagerModule.kt b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/NotificationChannelGroupManagerModule.kt index 859c90ed5e34bb..1d837ef903cbbe 100644 --- a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/NotificationChannelGroupManagerModule.kt +++ b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/NotificationChannelGroupManagerModule.kt @@ -23,8 +23,7 @@ open class NotificationChannelGroupManagerModule : Module(), NotificationsChanne AsyncFunction("getNotificationChannelGroupAsync") { groupId: String -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val group = groupManager.getNotificationChannelGroup(groupId) - groupSerializer.toBundle(group) + groupManager.getNotificationChannelGroup(groupId)?.let { groupSerializer.toBundle(it) } } else { null } diff --git a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/NotificationChannelManagerModule.kt b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/NotificationChannelManagerModule.kt index 2b6d2a24f0b352..4c6eb529ccb4ed 100644 --- a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/NotificationChannelManagerModule.kt +++ b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/NotificationChannelManagerModule.kt @@ -36,8 +36,7 @@ open class NotificationChannelManagerModule : Module(), NotificationsChannelProv AsyncFunction("getNotificationChannelAsync") { channelId: String -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val notificationChannel = channelManager.getNotificationChannel(channelId) - channelSerializer.toBundle(notificationChannel) + channelManager.getNotificationChannel(channelId)?.let { channelSerializer.toBundle(it) } } else { null } diff --git a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/serializers/ExpoNotificationsChannelGroupSerializer.java b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/serializers/ExpoNotificationsChannelGroupSerializer.java index ec8ee2e09a6bc9..4efec8ff719861 100644 --- a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/serializers/ExpoNotificationsChannelGroupSerializer.java +++ b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/serializers/ExpoNotificationsChannelGroupSerializer.java @@ -21,12 +21,9 @@ public ExpoNotificationsChannelGroupSerializer(NotificationsChannelSerializer ch } @Override - @Nullable + @NonNull @RequiresApi(api = Build.VERSION_CODES.O) - public Bundle toBundle(@Nullable NotificationChannelGroup group) { - if (group == null) { - return null; - } + public Bundle toBundle(@NonNull NotificationChannelGroup group) { Bundle result = new Bundle(); result.putString(ID_KEY, getId(group)); result.putString(NAME_KEY, group.getName().toString()); diff --git a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/serializers/ExpoNotificationsChannelSerializer.java b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/serializers/ExpoNotificationsChannelSerializer.java index 6841ce089200a6..33731da5f2a8de 100644 --- a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/serializers/ExpoNotificationsChannelSerializer.java +++ b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/serializers/ExpoNotificationsChannelSerializer.java @@ -18,14 +18,10 @@ public class ExpoNotificationsChannelSerializer implements NotificationsChannelSerializer { + @NonNull @Override - @Nullable @RequiresApi(api = Build.VERSION_CODES.O) - public Bundle toBundle(@Nullable NotificationChannel channel) { - if (channel == null) { - return null; - } - + public Bundle toBundle(@NonNull NotificationChannel channel) { Bundle result = new Bundle(); result.putString(ID_KEY, getChannelId(channel)); result.putString(NAME_KEY, channel.getName().toString()); diff --git a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/serializers/NotificationsChannelGroupSerializer.java b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/serializers/NotificationsChannelGroupSerializer.java deleted file mode 100644 index c998f0ea3983df..00000000000000 --- a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/serializers/NotificationsChannelGroupSerializer.java +++ /dev/null @@ -1,20 +0,0 @@ -package expo.modules.notifications.notifications.channels.serializers; - -import android.app.NotificationChannelGroup; -import android.os.Build; -import android.os.Bundle; - -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; - -public interface NotificationsChannelGroupSerializer { - String ID_KEY = "id"; - String NAME_KEY = "name"; - String DESCRIPTION_KEY = "description"; - String IS_BLOCKED_KEY = "isBlocked"; - String CHANNELS_KEY = "channels"; - - @Nullable - @RequiresApi(api = Build.VERSION_CODES.O) - Bundle toBundle(@Nullable NotificationChannelGroup group); -} diff --git a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/serializers/NotificationsChannelGroupSerializer.kt b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/serializers/NotificationsChannelGroupSerializer.kt new file mode 100644 index 00000000000000..4570d751e8f520 --- /dev/null +++ b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/serializers/NotificationsChannelGroupSerializer.kt @@ -0,0 +1,19 @@ +package expo.modules.notifications.notifications.channels.serializers + +import android.app.NotificationChannelGroup +import android.os.Build +import android.os.Bundle +import androidx.annotation.RequiresApi + +interface NotificationsChannelGroupSerializer { + @RequiresApi(api = Build.VERSION_CODES.O) + fun toBundle(group: NotificationChannelGroup): Bundle + + companion object { + const val ID_KEY = "id" + const val NAME_KEY = "name" + const val DESCRIPTION_KEY = "description" + const val IS_BLOCKED_KEY = "isBlocked" + const val CHANNELS_KEY = "channels" + } +} diff --git a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/serializers/NotificationsChannelSerializer.java b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/serializers/NotificationsChannelSerializer.java deleted file mode 100644 index 50e7c702e79b14..00000000000000 --- a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/serializers/NotificationsChannelSerializer.java +++ /dev/null @@ -1,35 +0,0 @@ -package expo.modules.notifications.notifications.channels.serializers; - -import android.app.NotificationChannel; -import android.os.Build; -import android.os.Bundle; - -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; - -public interface NotificationsChannelSerializer { - String ID_KEY = "id"; - String NAME_KEY = "name"; - String IMPORTANCE_KEY = "importance"; - String BYPASS_DND_KEY = "bypassDnd"; - String DESCRIPTION_KEY = "description"; - String GROUP_ID_KEY = "groupId"; - String LIGHT_COLOR_KEY = "lightColor"; - String LOCKSCREEN_VISIBILITY_KEY = "lockscreenVisibility"; - String SHOW_BADGE_KEY = "showBadge"; - String SOUND_KEY = "sound"; - String SOUND_AUDIO_ATTRIBUTES_KEY = "audioAttributes"; - String VIBRATION_PATTERN_KEY = "vibrationPattern"; - String ENABLE_LIGHTS_KEY = "enableLights"; - String ENABLE_VIBRATE_KEY = "enableVibrate"; - - String AUDIO_ATTRIBUTES_USAGE_KEY = "usage"; - String AUDIO_ATTRIBUTES_CONTENT_TYPE_KEY = "contentType"; - String AUDIO_ATTRIBUTES_FLAGS_KEY = "flags"; - String AUDIO_ATTRIBUTES_FLAGS_ENFORCE_AUDIBILITY_KEY = "enforceAudibility"; - String AUDIO_ATTRIBUTES_FLAGS_HW_AV_SYNC_KEY = "requestHardwareAudioVideoSynchronization"; - - @Nullable - @RequiresApi(api = Build.VERSION_CODES.O) - Bundle toBundle(@Nullable NotificationChannel channel); -} diff --git a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/serializers/NotificationsChannelSerializer.kt b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/serializers/NotificationsChannelSerializer.kt new file mode 100644 index 00000000000000..aae295c3e824fa --- /dev/null +++ b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/channels/serializers/NotificationsChannelSerializer.kt @@ -0,0 +1,34 @@ +package expo.modules.notifications.notifications.channels.serializers + +import android.app.NotificationChannel +import android.os.Build +import android.os.Bundle +import androidx.annotation.RequiresApi + +interface NotificationsChannelSerializer { + @RequiresApi(api = Build.VERSION_CODES.O) + fun toBundle(channel: NotificationChannel): Bundle + + companion object { + const val ID_KEY = "id" + const val NAME_KEY = "name" + const val IMPORTANCE_KEY = "importance" + const val BYPASS_DND_KEY = "bypassDnd" + const val DESCRIPTION_KEY = "description" + const val GROUP_ID_KEY = "groupId" + const val LIGHT_COLOR_KEY = "lightColor" + const val LOCKSCREEN_VISIBILITY_KEY = "lockscreenVisibility" + const val SHOW_BADGE_KEY = "showBadge" + const val SOUND_KEY = "sound" + const val SOUND_AUDIO_ATTRIBUTES_KEY = "audioAttributes" + const val VIBRATION_PATTERN_KEY = "vibrationPattern" + const val ENABLE_LIGHTS_KEY = "enableLights" + const val ENABLE_VIBRATE_KEY = "enableVibrate" + + const val AUDIO_ATTRIBUTES_USAGE_KEY = "usage" + const val AUDIO_ATTRIBUTES_CONTENT_TYPE_KEY = "contentType" + const val AUDIO_ATTRIBUTES_FLAGS_KEY = "flags" + const val AUDIO_ATTRIBUTES_FLAGS_ENFORCE_AUDIBILITY_KEY = "enforceAudibility" + const val AUDIO_ATTRIBUTES_FLAGS_HW_AV_SYNC_KEY = "requestHardwareAudioVideoSynchronization" + } +} diff --git a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/emitting/NotificationsEmitter.kt b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/emitting/NotificationsEmitter.kt index 6a78c84d598ca9..af020ccaa28c2e 100644 --- a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/emitting/NotificationsEmitter.kt +++ b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/emitting/NotificationsEmitter.kt @@ -72,7 +72,7 @@ open class NotificationsEmitter : Module(), NotificationListener { return true } - override fun onNotificationResponseIntentReceived(extras: Bundle?) { + override fun onNotificationResponseIntentReceived(extras: Bundle) { val bundle = NotificationSerializer.toResponseBundleFromExtras(extras) DebugLogging.logBundle("NotificationsEmitter.onNotificationResponseIntentReceived", bundle) lastNotificationResponseBundle = bundle diff --git a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/NotificationListener.java b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/NotificationListener.java deleted file mode 100644 index ec4bbfee76bb18..00000000000000 --- a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/NotificationListener.java +++ /dev/null @@ -1,47 +0,0 @@ -package expo.modules.notifications.notifications.interfaces; - -import android.os.Bundle; - -import com.google.firebase.messaging.FirebaseMessagingService; - -import expo.modules.notifications.notifications.model.Notification; -import expo.modules.notifications.notifications.model.NotificationResponse; - -/** - * Interface used to register in {@link NotificationManager} - * and be notified of new message events. - */ -public interface NotificationListener { - /** - * Callback called when new notification is received while the app is in foreground. - * - * @param notification Notification received - */ - default void onNotificationReceived(Notification notification) { - } - - /** - * Callback called when new notification response is received. - * - * @param response Notification response received - * @return Whether the notification response has been handled - */ - default boolean onNotificationResponseReceived(NotificationResponse response) { - return false; - } - - /** - * Callback called when notification response is received through package lifecycle listeners - * - * @param extras Bundle of extras from the lifecycle method - */ - default void onNotificationResponseIntentReceived(Bundle extras) { - } - - /** - * Callback called when some notifications are dropped. - * See {@link FirebaseMessagingService#onDeletedMessages()} - */ - default void onNotificationsDropped() { - } -} diff --git a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/NotificationListener.kt b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/NotificationListener.kt new file mode 100644 index 00000000000000..10392b94027261 --- /dev/null +++ b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/NotificationListener.kt @@ -0,0 +1,26 @@ +package expo.modules.notifications.notifications.interfaces + +import android.os.Bundle +import expo.modules.notifications.notifications.model.Notification +import expo.modules.notifications.notifications.model.NotificationResponse + +/** + * Interface used to register in [NotificationManager][expo.modules.notifications.notifications.NotificationManager] + * and be notified of new message events. + */ +interface NotificationListener { + /** Callback called when new notification is received while the app is in foreground. */ + fun onNotificationReceived(notification: Notification) {} + + /** + * Callback called when new notification response is received. + * @return Whether the notification response has been handled + */ + fun onNotificationResponseReceived(response: NotificationResponse): Boolean = false + + /** Callback called when notification response is received through package lifecycle listeners. */ + fun onNotificationResponseIntentReceived(extras: Bundle) {} + + /** Callback called when some notifications are dropped. */ + fun onNotificationsDropped() {} +} diff --git a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/SchedulableNotificationTrigger.java b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/SchedulableNotificationTrigger.java deleted file mode 100644 index b5cf7ab386f016..00000000000000 --- a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/SchedulableNotificationTrigger.java +++ /dev/null @@ -1,20 +0,0 @@ -package expo.modules.notifications.notifications.interfaces; - -import java.io.Serializable; -import java.util.Date; - -import androidx.annotation.Nullable; -import expo.modules.notifications.service.delegates.SharedPreferencesNotificationsStore; - -/** - * A notification trigger that is serializable - this ensures {@link SharedPreferencesNotificationsStore} - * is capable of storing it in the device's memory. - */ -public interface SchedulableNotificationTrigger extends NotificationTrigger, Serializable { - /** - * @return Next date at which the notification should be triggered. Returns `null` - * if the notification will not trigger in the future (it can be removed then). - */ - @Nullable - Date nextTriggerDate(); -} diff --git a/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/SchedulableNotificationTrigger.kt b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/SchedulableNotificationTrigger.kt new file mode 100644 index 00000000000000..9d8cdb399fbbaf --- /dev/null +++ b/packages/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/SchedulableNotificationTrigger.kt @@ -0,0 +1,17 @@ +package expo.modules.notifications.notifications.interfaces + +import expo.modules.notifications.service.delegates.SharedPreferencesNotificationsStore +import java.io.Serializable +import java.util.Date + +/** + * A notification trigger that is serializable - this ensures [SharedPreferencesNotificationsStore] + * is capable of storing it in the device's memory. + */ +interface SchedulableNotificationTrigger : NotificationTrigger, Serializable { + /** + * @return Next date at which the notification should be triggered. Returns `null` + * if the notification will not trigger in the future (it can be removed then). + */ + fun nextTriggerDate(): Date? +} From c8a00ae609acead762f19e8bf834ce328df05fb9 Mon Sep 17 00:00:00 2001 From: Jakub Tkacz <32908614+Ubax@users.noreply.github.com> Date: Thu, 19 Feb 2026 08:12:06 +0100 Subject: [PATCH 2/7] [expo-router][ios] fix gestureEnabled: false when using zoom transition (#43202) # Why Setting `gestureEnabled` had no effect when used with zoom transition **Before** https://github.com/user-attachments/assets/db45a5f4-11ac-463a-a6fc-22c25108fbf3 ** After** https://github.com/user-attachments/assets/2f5685e3-3ed2-44f0-8e0b-4ba6a2b5545f # How When `gesturesEnabled` are set to `false` then pass `{maxX:0,maxY:0}` to `dismissalBoundsRect`. The `maxX:0, maxY:0` means that the dismiss can start at coordinates that are smaller then `0,0`, which effectively block the dismissal. # Test Plan 1. Unit tests 2. Manual testing # Checklist - [ ] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [ ] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [ ] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --- .../link-preview/app/zoom-dest-contain.tsx | 22 ++- packages/expo-router/CHANGELOG.md | 4 +- .../zoom/ZoomTransitionEnabler.ios.d.ts.map | 2 +- .../link/zoom/ZoomTransitionEnabler.ios.js | 11 +- .../zoom/ZoomTransitionEnabler.ios.js.map | 2 +- .../link/zoom/ZoomTransitionEnabler.ios.tsx | 13 +- .../ZoomTransitionEnabler.e2e.test.ios.tsx | 164 ++++++++++++++++++ .../ZoomTransitionEnabler.test.ios.tsx | 131 ++++++++++++++ 8 files changed, 340 insertions(+), 9 deletions(-) create mode 100644 packages/expo-router/src/link/zoom/__tests__/ZoomTransitionEnabler.e2e.test.ios.tsx create mode 100644 packages/expo-router/src/link/zoom/__tests__/ZoomTransitionEnabler.test.ios.tsx diff --git a/apps/router-e2e/__e2e__/link-preview/app/zoom-dest-contain.tsx b/apps/router-e2e/__e2e__/link-preview/app/zoom-dest-contain.tsx index c7b401361d647d..8a4e8ee5c96ce8 100644 --- a/apps/router-e2e/__e2e__/link-preview/app/zoom-dest-contain.tsx +++ b/apps/router-e2e/__e2e__/link-preview/app/zoom-dest-contain.tsx @@ -1,9 +1,27 @@ -import { Link } from 'expo-router'; -import { Image, View } from 'react-native'; +import { Link, Stack } from 'expo-router'; +import { useState } from 'react'; +import { Button, Image, View } from 'react-native'; export default function ZoomDestScreen() { + const [gesturesEnabled, setGesturesEnabled] = useState(true); return ( + + +