From b7f1dda355f1dca181937732c06b64fa4abcbcf4 Mon Sep 17 00:00:00 2001 From: Chayel J Heinsen Date: Fri, 23 Jan 2026 10:19:21 -0500 Subject: [PATCH 1/3] Add support for tracking push notification opens - iOS [sc-184466] --- example/ios/MovableInkExample/AppDelegate.mm | 16 ++++++++++ .../MovableInkExample.entitlements | 2 ++ example/package.json | 1 + example/src/App.tsx | 32 +++++++++++++++++++ example/yarn.lock | 11 +++++++ ios/RNMovableInk.m | 1 + ios/RNMovableInk.swift | 8 +++++ react-native-movable-ink.podspec | 4 +-- src/index.tsx | 1 + 9 files changed, 74 insertions(+), 2 deletions(-) diff --git a/example/ios/MovableInkExample/AppDelegate.mm b/example/ios/MovableInkExample/AppDelegate.mm index c0f10d2..c5e839d 100644 --- a/example/ios/MovableInkExample/AppDelegate.mm +++ b/example/ios/MovableInkExample/AppDelegate.mm @@ -3,6 +3,7 @@ #import #import #import +#import "RNNotifications.h" @implementation AppDelegate @@ -13,6 +14,8 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( // They will be passed down to the ViewController used by React Native. self.initialProps = @{}; + [RNNotifications startMonitorNotifications]; + return [super application:application didFinishLaunchingWithOptions:launchOptions]; } @@ -27,6 +30,19 @@ - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserAct restorationHandler:restorationHandler]; } +- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + NSLog(@"Device Token: %@", deviceToken); + [RNNotifications didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; +} + +- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { + [RNNotifications didFailToRegisterForRemoteNotificationsWithError:error]; +} + +- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { + [RNNotifications didReceiveBackgroundNotification:userInfo withCompletionHandler:completionHandler]; +} + - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge { #if DEBUG diff --git a/example/ios/MovableInkExample/MovableInkExample.entitlements b/example/ios/MovableInkExample/MovableInkExample.entitlements index 826b6f6..568df8c 100644 --- a/example/ios/MovableInkExample/MovableInkExample.entitlements +++ b/example/ios/MovableInkExample/MovableInkExample.entitlements @@ -2,6 +2,8 @@ + aps-environment + development com.apple.developer.associated-domains applinks:mi-mobile-sandbox.mimobile.xyz diff --git a/example/package.json b/example/package.json index 882e400..bfe1405 100644 --- a/example/package.json +++ b/example/package.json @@ -11,6 +11,7 @@ "dependencies": { "react": "18.2.0", "react-native": "0.73.5", + "react-native-notifications": "^5.2.2", "react-native-svg": "^15.1.0", "react-native-svg-transformer": "^1.3.0" }, diff --git a/example/src/App.tsx b/example/src/App.tsx index 84477a3..fd2c779 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { StyleSheet, View, Text, Linking, Button } from 'react-native'; import RNMovableInk, { Currency } from '@movable/react-native-sdk'; +import { Notifications, type Registered } from 'react-native-notifications'; export default function App() { const [link, setLink] = React.useState(); @@ -13,6 +14,34 @@ export default function App() { RNMovableInk.start(); RNMovableInk.setMIU('00000000-00000000-00000000-00000000'); + // Register for remote notifications + Notifications.registerRemoteNotifications(); + + // Handle device token updates + const registerForNotificationsSubscription = Notifications.events().registerRemoteNotificationsRegistered((event: Registered) => { + console.log(event.deviceToken); + }); + + // Handle notifications received in foreground + const foregroundSubscription = Notifications.events().registerNotificationReceivedForeground( + (notification, completion) => { + console.log(`Notification received in foreground: ${notification.title} : ${notification.body}`); + completion({ alert: false, sound: false, badge: false }); + } + ); + + // Handle notification taps + const openedSubscription = Notifications.events().registerNotificationOpened( + (notification, completion) => { + console.log('Notification opened:'); + console.log(JSON.stringify(notification.payload)); + + RNMovableInk.handlePushNotificationOpenedWithContent(notification.payload); + + completion(); + } + ); + // Get the deep link used to open the app const getInitialURL = async () => { const universalLink = await Linking.getInitialURL(); @@ -42,6 +71,9 @@ export default function App() { return () => { urlListener.remove(); + registerForNotificationsSubscription.remove(); + foregroundSubscription?.remove(); + openedSubscription?.remove(); }; }, []); diff --git a/example/yarn.lock b/example/yarn.lock index dd7d76a..7590d92 100644 --- a/example/yarn.lock +++ b/example/yarn.lock @@ -5259,6 +5259,7 @@ __metadata: babel-plugin-module-resolver: "npm:^5.0.0" react: "npm:18.2.0" react-native: "npm:0.73.5" + react-native-notifications: "npm:^5.2.2" react-native-svg: "npm:^15.1.0" react-native-svg-transformer: "npm:^1.3.0" languageName: unknown @@ -5858,6 +5859,16 @@ __metadata: languageName: node linkType: hard +"react-native-notifications@npm:^5.2.2": + version: 5.2.2 + resolution: "react-native-notifications@npm:5.2.2" + peerDependencies: + react: "*" + react-native: "*" + checksum: 10c0/eeea388d1a338b62699c50baf231aa426309aee5ccfcf32d22322b5418a39de594b77a79fd245b9c767198830390183ae8869bf2fca3b3d30093d064826e7f7b + languageName: node + linkType: hard + "react-native-svg-transformer@npm:^1.3.0": version: 1.3.0 resolution: "react-native-svg-transformer@npm:1.3.0" diff --git a/ios/RNMovableInk.m b/ios/RNMovableInk.m index eea10f7..071eeaf 100644 --- a/ios/RNMovableInk.m +++ b/ios/RNMovableInk.m @@ -25,6 +25,7 @@ @interface RCT_EXTERN_MODULE(RNMovableInk, NSObject) RCT_EXTERN_METHOD(setAppInstallEventEnabled:(BOOL)enabled) RCT_EXTERN_METHOD(setValidPasteboardValues:(NSArray *)values) +RCT_EXTERN_METHOD(handlePushNotificationOpenedWithContent:(NSDictionary *)content) + (BOOL)requiresMainQueueSetup { return TRUE; diff --git a/ios/RNMovableInk.swift b/ios/RNMovableInk.swift index a697597..b8c44b3 100644 --- a/ios/RNMovableInk.swift +++ b/ios/RNMovableInk.swift @@ -153,4 +153,12 @@ public class RNMovableInk: NSObject { MIClient.validPasteboardValues = values } } + + @objc(handlePushNotificationOpenedWithContent:) + public func handlePushNotificationOpenedWithContent(content: [String: Any]) { + Task { @MainActor in + guard let content = content as? [String: Sendable] else { return } + MIClient.handlePushNotificationOpened(content) + } + } } diff --git a/react-native-movable-ink.podspec b/react-native-movable-ink.podspec index f81f827..09e7095 100644 --- a/react-native-movable-ink.podspec +++ b/react-native-movable-ink.podspec @@ -17,10 +17,10 @@ Pod::Spec.new do |s| s.source_files = "ios/**/*.{h,m,mm,swift}" # Enable when testing local pod - # s.dependency "MovableInk" + s.dependency "MovableInk" # Disable when testing local pod - s.dependency "MovableInk", "2.2.0" + # s.dependency "MovableInk", "2.3.0" s.dependency "React-Core" diff --git a/src/index.tsx b/src/index.tsx index 38c0a04..c682ead 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -242,6 +242,7 @@ export interface MovableInkInterface { checkPasteboardOnInstall(): Promise; showInAppMessage(url: string, callback: (buttonID: string) => void): void; setAppInstallEventEnabled(enabled: boolean): void; + handlePushNotificationOpenedWithContent(content: Record): void; } export default RNMovableInk as MovableInkInterface; From 23c8eb5f3fb1248af0dbe367553c269ac8b50efe Mon Sep 17 00:00:00 2001 From: Chayel J Heinsen Date: Mon, 26 Jan 2026 10:36:41 -0500 Subject: [PATCH 2/3] lint --- example/src/App.tsx | 49 +++++++++++++++++++++++++++------------------ src/index.tsx | 4 +++- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/example/src/App.tsx b/example/src/App.tsx index fd2c779..012cba4 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -18,29 +18,38 @@ export default function App() { Notifications.registerRemoteNotifications(); // Handle device token updates - const registerForNotificationsSubscription = Notifications.events().registerRemoteNotificationsRegistered((event: Registered) => { - console.log(event.deviceToken); - }); + const registerForNotificationsSubscription = + Notifications.events().registerRemoteNotificationsRegistered( + (event: Registered) => { + console.log(event.deviceToken); + } + ); // Handle notifications received in foreground - const foregroundSubscription = Notifications.events().registerNotificationReceivedForeground( - (notification, completion) => { - console.log(`Notification received in foreground: ${notification.title} : ${notification.body}`); - completion({ alert: false, sound: false, badge: false }); - } - ); + const foregroundSubscription = + Notifications.events().registerNotificationReceivedForeground( + (notification, completion) => { + console.log( + `Notification received in foreground: ${notification.title} : ${notification.body}` + ); + completion({ alert: false, sound: false, badge: false }); + } + ); // Handle notification taps - const openedSubscription = Notifications.events().registerNotificationOpened( - (notification, completion) => { - console.log('Notification opened:'); - console.log(JSON.stringify(notification.payload)); - - RNMovableInk.handlePushNotificationOpenedWithContent(notification.payload); + const openedSubscription = + Notifications.events().registerNotificationOpened( + (notification, completion) => { + console.log('Notification opened:'); + console.log(JSON.stringify(notification.payload)); + + RNMovableInk.handlePushNotificationOpenedWithContent( + notification.payload + ); - completion(); - } - ); + completion(); + } + ); // Get the deep link used to open the app const getInitialURL = async () => { @@ -141,7 +150,9 @@ export default function App() {