diff --git a/.github/workflows/lint_and_test.yml b/.github/workflows/lint_and_test.yml index 3af9449..00998d1 100644 --- a/.github/workflows/lint_and_test.yml +++ b/.github/workflows/lint_and_test.yml @@ -21,7 +21,7 @@ jobs: - name: Setup node JS uses: actions/setup-node@v2 with: - node-version: 14 + node-version: 18 registry-url: https://registry.npmjs.org - name: Setup local environment @@ -39,7 +39,7 @@ jobs: - name: Setup node JS uses: actions/setup-node@v2 with: - node-version: 14 + node-version: 18 registry-url: https://registry.npmjs.org - name: Setup local environment diff --git a/android/build.gradle b/android/build.gradle index 605b2b1..93a80c3 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,6 +1,7 @@ buildscript { - // Buildscript is evaluated before everything else so we can't use getExtOrDefault - def kotlin_version = rootProject.ext.has('kotlinVersion') ? rootProject.ext.get('kotlinVersion') : project.properties['MindboxSdk_kotlinVersion'] + ext.safeExtGet = { prop, fallback -> + rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback + } repositories { google() @@ -8,123 +9,74 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.2.1' - // noinspection DifferentKotlinGradleVersion - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath('com.android.tools.build:gradle:8.6.0') + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${safeExtGet('kotlinVersion', '1.9.22')}") } } +apply plugin: "com.facebook.react" apply plugin: 'com.android.library' apply plugin: 'kotlin-android' -def getExtOrDefault(name) { - return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['MindboxSdk_' + name] +def readBooleanGradleProperty = { String name -> + def value = project.findProperty(name) + if (value == null) { + value = rootProject.findProperty(name) + } + if (value == null) { + return null + } + return value.toString().toBoolean() } -def getExtOrIntegerDefault(name) { - return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['MindboxSdk_' + name]).toInteger() +def isNewArchEnabled = readBooleanGradleProperty("newArchEnabled") + +if (isNewArchEnabled == false) { + throw new GradleException( + "${project.name}: Mindbox SDK supports only React Native New Architecture on Android.\n" + + "Remove `newArchEnabled=false` from gradle.properties or enable the New Architecture." + ) } android { - compileSdkVersion getExtOrIntegerDefault('compileSdkVersion') - buildToolsVersion getExtOrDefault('buildToolsVersion') + namespace 'com.mindboxsdk' + compileSdkVersion safeExtGet('compileSdkVersion', 35) + defaultConfig { - minSdkVersion 21 - targetSdkVersion getExtOrIntegerDefault('targetSdkVersion') + minSdkVersion safeExtGet('minSdkVersion', 24) + targetSdkVersion safeExtGet('targetSdkVersion', 35) versionCode 1 - versionName "1.0" + versionName '1.0' consumerProguardFiles 'consumer-rules.pro' } - buildTypes { - release { - minifyEnabled false - } - } - lintOptions { - disable 'GradleCompatible' - } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } -} - -repositories { - mavenCentral() - google() - def found = false - def defaultDir = null - def androidSourcesName = 'React Native sources' - - if (rootProject.ext.has('reactNativeAndroidRoot')) { - defaultDir = rootProject.ext.get('reactNativeAndroidRoot') - } else { - defaultDir = new File( - projectDir, - '/../../../node_modules/react-native/android' - ) + kotlinOptions { + jvmTarget = '17' } - if (defaultDir.exists()) { - maven { - url defaultDir.toString() - name androidSourcesName + sourceSets { + main { + java.srcDirs += ["$buildDir/generated/source/codegen/java"] } - - logger.info(":${project.name}:reactNativeAndroidRoot ${defaultDir.canonicalPath}") - found = true - } else { - def parentDir = rootProject.projectDir - - 1.upto(5, { - if (found) return true - parentDir = parentDir.parentFile - - def androidSourcesDir = new File( - parentDir, - 'node_modules/react-native' - ) - - def androidPrebuiltBinaryDir = new File( - parentDir, - 'node_modules/react-native/android' - ) - - if (androidPrebuiltBinaryDir.exists()) { - maven { - url androidPrebuiltBinaryDir.toString() - name androidSourcesName - } - - logger.info(":${project.name}:reactNativeAndroidRoot ${androidPrebuiltBinaryDir.canonicalPath}") - found = true - } else if (androidSourcesDir.exists()) { - maven { - url androidSourcesDir.toString() - name androidSourcesName - } - - logger.info(":${project.name}:reactNativeAndroidRoot ${androidSourcesDir.canonicalPath}") - found = true - } - }) } - if (!found) { - throw new GradleException( - "${project.name}: unable to locate React Native android sources. " + - "Ensure you have you installed React Native as a dependency in your project and try again." - ) + lintOptions { + abortOnError false } } -def kotlin_version = getExtOrDefault('kotlinVersion') +repositories { + mavenCentral() + google() +} dependencies { - // noinspection GradleDynamicVersion - api 'com.facebook.react:react-native:+' - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'com.facebook.react:react-native:+' + implementation "org.jetbrains.kotlin:kotlin-stdlib:${safeExtGet('kotlinVersion', '1.9.22')}" api 'cloud.mindbox:mobile-sdk:2.15.0' } diff --git a/android/src/main/java/com/mindboxsdk/MindboxEventEmitter.kt b/android/src/main/java/com/mindboxsdk/MindboxEventEmitter.kt index 5a6de8e..97b58b1 100644 --- a/android/src/main/java/com/mindboxsdk/MindboxEventEmitter.kt +++ b/android/src/main/java/com/mindboxsdk/MindboxEventEmitter.kt @@ -2,18 +2,13 @@ package com.mindboxsdk import android.app.Activity import android.app.Application -import android.content.Context import android.content.Intent -import android.util.Log -import com.facebook.react.ReactApplication -import com.facebook.react.ReactInstanceManager import com.facebook.react.bridge.ReactContext -import com.facebook.react.ReactActivity import cloud.mindbox.mobile_sdk.Mindbox import cloud.mindbox.mobile_sdk.logger.Level -internal class MindboxEventEmitter ( - private val application: Application +internal class MindboxEventEmitter( + private val application: Application, ) : MindboxEventSubscriber { private var jsDelivery: MindboxJsDelivery? = null @@ -33,7 +28,7 @@ internal class MindboxEventEmitter ( jsDelivery?.sendPushClicked(intent) } - private fun handleActivityCreated(reactContext:ReactContext, activity: Activity) { + private fun handleActivityCreated(reactContext: ReactContext, activity: Activity) { Mindbox.writeLog("[RN] Handle activity created", Level.INFO) runCatching { reactContext.let { reactContext -> @@ -45,7 +40,7 @@ internal class MindboxEventEmitter ( private fun initializeAndSendIntent(context: ReactContext, activity: Activity) { Mindbox.writeLog("[RN] Initialize MindboxJsDelivery", Level.INFO) jsDelivery = MindboxJsDelivery.Shared.getInstance(context) - val currentActivity = context.currentActivity ?: activity + val currentActivity: Activity = context.currentActivity ?: activity currentActivity.intent?.let { handleNewIntent(context, it) } } diff --git a/android/src/main/java/com/mindboxsdk/MindboxEventSubscriber.kt b/android/src/main/java/com/mindboxsdk/MindboxEventSubscriber.kt index ad7105f..6cc0600 100644 --- a/android/src/main/java/com/mindboxsdk/MindboxEventSubscriber.kt +++ b/android/src/main/java/com/mindboxsdk/MindboxEventSubscriber.kt @@ -1,5 +1,5 @@ package com.mindboxsdk -internal interface MindboxEventSubscriber { +internal fun interface MindboxEventSubscriber { fun onEvent(event: MindboxSdkLifecycleEvent) } diff --git a/android/src/main/java/com/mindboxsdk/MindboxJsDelivery.kt b/android/src/main/java/com/mindboxsdk/MindboxJsDelivery.kt index a323107..981c3f1 100644 --- a/android/src/main/java/com/mindboxsdk/MindboxJsDelivery.kt +++ b/android/src/main/java/com/mindboxsdk/MindboxJsDelivery.kt @@ -3,67 +3,58 @@ package com.mindboxsdk import android.content.Intent import android.os.Bundle import com.facebook.react.bridge.ReactContext -import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter -import org.json.JSONObject import kotlin.properties.Delegates import cloud.mindbox.mobile_sdk.Mindbox import cloud.mindbox.mobile_sdk.logger.Level class MindboxJsDelivery private constructor(private val mReactContext: ReactContext) { - companion object Shared { - private var INSTANCE: MindboxJsDelivery? = null - private var delayedIntent: Intent? = null + companion object Shared { + private var INSTANCE: MindboxJsDelivery? = null + private var delayedIntent: Intent? = null - var hasListeners: Boolean by Delegates.observable(false) { _, _, newValue -> - if (newValue) { - delayedIntent?.let { - it.extras?.let { - Mindbox.writeLog("[RN] Send push data from delayed ${it}", Level.INFO) + var hasListeners: Boolean by Delegates.observable(false) { _, _, newValue -> + Mindbox.writeLog("[RN][MindboxJsDelivery] hasListeners=$newValue", Level.DEBUG) + if (newValue) { + delayedIntent?.let { intent -> + intent.extras?.let { + Mindbox.writeLog("[RN] Send push data from delayed ${it}", Level.INFO) + } + INSTANCE?.sendPushClicked(intent) + } } - INSTANCE?.sendPushClicked(it) + delayedIntent = null } - } - delayedIntent = null - } - fun getInstance(reactContext: ReactContext): MindboxJsDelivery? { - if (INSTANCE == null) { - synchronized(MindboxJsDelivery::class.java) { - if (INSTANCE == null) { - INSTANCE = MindboxJsDelivery(reactContext) - } + fun getInstance(reactContext: ReactContext): MindboxJsDelivery? { + if (INSTANCE == null) { + synchronized(MindboxJsDelivery::class.java) { + if (INSTANCE == null) { + INSTANCE = MindboxJsDelivery(reactContext) + } + } + } + return INSTANCE } - } - - return INSTANCE } - } - private fun sendEvent(eventName: String, bundle: Bundle) { - if (mReactContext.hasActiveCatalystInstance()) { - var payload = JSONObject(); - payload.put("pushUrl", bundle.getString("push_url", "")); - payload.put("pushPayload", bundle.getString("push_payload", "")); - Mindbox.writeLog("[RN] Send push data to listener with ${payload.toString()}", Level.INFO) - mReactContext - .getJSModule(RCTDeviceEventEmitter::class.java) - .emit(eventName, payload.toString()) + private fun sendEvent(eventName: String, bundle: Bundle) { + Mindbox.writeLog("[RN][MindboxJsDelivery] sendEvent($eventName) push_url=${bundle.getString("push_url")}", Level.INFO) + MindboxSdkModule.deliverPushNotificationClickedFromExternal(bundle, mReactContext) } - } - fun sendPushClicked(intent: Intent) { - if (hasListeners) { - val bundle = intent.extras - if (bundle != null) { - val key = bundle.getString("uniq_push_key") - if (key != null) { - sendEvent("pushNotificationClicked", bundle) + fun sendPushClicked(intent: Intent) { + if (hasListeners) { + val bundle = intent.extras + if (bundle != null) { + val key = bundle.getString("uniq_push_key") + if (key != null) { + sendEvent("pushNotificationClicked", bundle) + } else { + Mindbox.writeLog("[RN] Push without uniq_push_key — ignored", Level.INFO) + } + } } else { - Mindbox.writeLog("[RN] Push was received without uniq_push_key it is not our push", Level.INFO) + delayedIntent = intent } - } - } else { - delayedIntent = intent } - } } diff --git a/android/src/main/java/com/mindboxsdk/MindboxSdkModule.kt b/android/src/main/java/com/mindboxsdk/MindboxSdkModule.kt index be9237c..6714903 100644 --- a/android/src/main/java/com/mindboxsdk/MindboxSdkModule.kt +++ b/android/src/main/java/com/mindboxsdk/MindboxSdkModule.kt @@ -2,11 +2,17 @@ package com.mindboxsdk import android.app.Activity import android.content.Context +import android.os.Bundle import android.os.Handler +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.bridge.ReactContextBaseJavaModule -import com.facebook.react.bridge.ReactMethod - +import com.facebook.react.bridge.ReactContext +import com.facebook.react.bridge.ReadableArray +import com.facebook.react.bridge.WritableMap +import com.facebook.react.module.annotations.ReactModule +import cloud.mindbox.mobile_sdk.Mindbox +import cloud.mindbox.mobile_sdk.MindboxConfiguration import cloud.mindbox.mobile_sdk.inapp.presentation.InAppCallback import cloud.mindbox.mobile_sdk.inapp.presentation.callbacks.ComposableInAppCallback import cloud.mindbox.mobile_sdk.inapp.presentation.callbacks.CopyPayloadInAppCallback @@ -14,229 +20,242 @@ import cloud.mindbox.mobile_sdk.inapp.presentation.callbacks.DeepLinkInAppCallba import cloud.mindbox.mobile_sdk.inapp.presentation.callbacks.EmptyInAppCallback import cloud.mindbox.mobile_sdk.inapp.presentation.callbacks.LoggingInAppCallback import cloud.mindbox.mobile_sdk.inapp.presentation.callbacks.UrlInAppCallback -import cloud.mindbox.mobile_sdk.Mindbox import cloud.mindbox.mobile_sdk.logger.Level -import cloud.mindbox.mobile_sdk.MindboxConfiguration -import com.facebook.react.bridge.Promise -import com.facebook.react.bridge.WritableMap -import com.facebook.react.bridge.Arguments -import com.facebook.react.bridge.ReadableArray -import com.facebook.react.modules.core.DeviceEventManagerModule import org.json.JSONObject -class MindboxSdkModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { - private var deviceUuidSubscription: String? = null - private var fmsTokenSubscription: String? = null - private var getTokensSubscription: String? = null - - override fun getName(): String { - return "MindboxSdk" - } - - @ReactMethod - fun initialize(payloadString: String, promise: Promise) { - try { - val payload = JSONObject(payloadString) - val context: Context = reactApplicationContext.applicationContext - val activity: Activity? = reactApplicationContext.currentActivity - - if (activity != null && context != null) { - val configurationBuilder = MindboxConfiguration.Builder( - context = context, - domain = payload.optString("domain", "api.mindbox.ru"), - endpointId = payload.optString("endpointId", "") - ) +@ReactModule(name = NativeMindboxSdkSpec.NAME) +class MindboxSdkModule( + private val reactContext: ReactApplicationContext +) : NativeMindboxSdkSpec(reactContext) { - if (payload.has("subscribeCustomerIfCreated")) { - configurationBuilder.subscribeCustomerIfCreated(payload.optBoolean("subscribeCustomerIfCreated", false)) - } - if (payload.has("shouldCreateCustomer")) { - configurationBuilder.shouldCreateCustomer(payload.optBoolean("shouldCreateCustomer", true)) + companion object { + @Volatile + private var activeModule: MindboxSdkModule? = null + + private fun setActiveModule(module: MindboxSdkModule) { + activeModule = module } - if (payload.has("previousInstallId")) { - configurationBuilder.setPreviousInstallationId(payload.optString("previousInstallId", "")) + + private fun clearActiveModule(module: MindboxSdkModule) { + if (activeModule === module) { + activeModule = null + } } - if (payload.has("previousUuid")) { - configurationBuilder.setPreviousDeviceUuid(payload.optString("previousUuid", "")) + + fun deliverPushNotificationClickedFromExternal(bundle: Bundle, fallbackReactContext: ReactContext) { + val module: MindboxSdkModule? = activeModule + if (module != null) { + module.emitPushFromDelivery(bundle) + } else { + Mindbox.writeLog("[RN][MindboxSdkModule] deliverPush: no active module, event skipped", Level.WARN) + } } - val configuration = configurationBuilder.build() + } - val handler = Handler(context.mainLooper) - handler.post(Runnable { - Mindbox.init(activity, configuration, listOf()) - }) + init { + setActiveModule(this) + } - promise.resolve(true) - } else { - promise.resolve(false) - } - } catch (error: Throwable) { - promise.reject(error) - } - } - - @ReactMethod - fun registerCallbacks( - callbacks: ReadableArray - ) { - val cb = mutableListOf() - for (i in 0 until callbacks.size()) { - when (val callback = callbacks.getString(i)) { - "urlInAppCallback" -> { - cb.add(UrlInAppCallback()) - cb.add(DeepLinkInAppCallback()) - cb.add(LoggingInAppCallback()) - } + private var deviceUuidSubscription: String? = null + private var fmsTokenSubscription: String? = null + private var getTokensSubscription: String? = null - "copyPayloadInAppCallback" -> { - cb.add(CopyPayloadInAppCallback()) - cb.add(LoggingInAppCallback()) + private fun emitPushFromDelivery(bundle: Bundle) { + val payload: WritableMap = Arguments.createMap().apply { + putString("pushUrl", bundle.getString("push_url", "")) + putString("pushPayload", bundle.getString("push_payload", "")) } + emitOnPushNotificationClicked(payload) + } - "emptyInAppCallback" -> { - cb.add(EmptyInAppCallback()) + override fun initialize(payloadString: String, promise: Promise) { + try { + val payload = JSONObject(payloadString) + val context: Context = reactApplicationContext.applicationContext + val activity: Activity? = reactApplicationContext.currentActivity + if (activity != null) { + val configurationBuilder = MindboxConfiguration.Builder( + context = context, + domain = payload.optString("domain", "api.mindbox.ru"), + endpointId = payload.optString("endpointId", "") + ) + if (payload.has("subscribeCustomerIfCreated")) { + configurationBuilder.subscribeCustomerIfCreated( + payload.optBoolean("subscribeCustomerIfCreated", false) + ) + } + if (payload.has("shouldCreateCustomer")) { + configurationBuilder.shouldCreateCustomer( + payload.optBoolean("shouldCreateCustomer", true) + ) + } + if (payload.has("previousInstallId")) { + configurationBuilder.setPreviousInstallationId( + payload.optString("previousInstallId", "") + ) + } + if (payload.has("previousUuid")) { + configurationBuilder.setPreviousDeviceUuid( + payload.optString("previousUuid", "") + ) + } + val configuration = configurationBuilder.build() + val handler = Handler(context.mainLooper) + handler.post { + Mindbox.init(activity, configuration, listOf()) + } + promise.resolve(true) + } else { + promise.resolve(false) + } + } catch (error: Throwable) { + promise.reject(error) } + } - else -> { - cb.add(object : InAppCallback { - override fun onInAppClick(id: String, redirectUrl: String, payload: String) { - val params = Arguments.createMap().apply { - putString("id", id) - putString("redirectUrl", redirectUrl) - putString("payload", payload) + override fun registerCallbacks(callbacks: ReadableArray) { + val cb = mutableListOf() + for (i in 0 until callbacks.size()) { + when (callbacks.getString(i)) { + "urlInAppCallback" -> { + cb.add(UrlInAppCallback()) + cb.add(DeepLinkInAppCallback()) + cb.add(LoggingInAppCallback()) + } + "copyPayloadInAppCallback" -> { + cb.add(CopyPayloadInAppCallback()) + cb.add(LoggingInAppCallback()) + } + "emptyInAppCallback" -> { + cb.add(EmptyInAppCallback()) + } + else -> { + cb.add(object : InAppCallback { + override fun onInAppClick(id: String, redirectUrl: String, payload: String) { + val params = Arguments.createMap().apply { + putString("id", id) + putString("redirectUrl", redirectUrl) + putString("payload", payload) + } + emitOnInAppClick(params) + } + override fun onInAppDismissed(id: String) { + val params = Arguments.createMap().apply { + putString("id", id) + } + emitOnInAppDismiss(params) + } + }) } - reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java).emit("Click", params) } + } + Mindbox.registerInAppCallback(ComposableInAppCallback(cb)) + } - override fun onInAppDismissed(id: String) { - val params = Arguments.createMap().apply { - putString("id", id) - } + override fun getDeviceUUID(promise: Promise) { + try { + if (deviceUuidSubscription != null) { + Mindbox.disposeDeviceUuidSubscription(deviceUuidSubscription!!) + } + deviceUuidSubscription = Mindbox.subscribeDeviceUuid { deviceUUID -> + promise.resolve(deviceUUID) + } + } catch (error: Throwable) { + promise.reject(error) + } + } + + override fun getFMSToken(promise: Promise) { + try { + if (fmsTokenSubscription != null) { + Mindbox.disposePushTokenSubscription(fmsTokenSubscription!!) + } + fmsTokenSubscription = Mindbox.subscribePushToken { fmsToken -> + promise.resolve(fmsToken) + } + } catch (error: Throwable) { + promise.reject(error) + } + } - reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java).emit("Dismiss", params) + override fun getTokens(promise: Promise) { + try { + if (getTokensSubscription != null) { + Mindbox.disposePushTokenSubscription(getTokensSubscription!!) + } + getTokensSubscription = Mindbox.subscribePushTokens { tokens -> + promise.resolve(tokens) } - }) + } catch (error: Throwable) { + promise.reject(error) + } + } + + override fun updateFMSToken(token: String, promise: Promise) { + try { + Mindbox.updateNotificationPermissionStatus(reactApplicationContext.applicationContext) + promise.resolve(true) + } catch (error: Throwable) { + promise.reject(error) + } + } + + override fun executeAsyncOperation(operationSystemName: String, operationBody: String, promise: Promise) { + Mindbox.executeAsyncOperation( + reactApplicationContext.applicationContext, + operationSystemName, + operationBody + ) + promise.resolve(true) + } + + override fun executeSyncOperation(operationSystemName: String, operationBody: String, promise: Promise) { + Mindbox.executeSyncOperation( + context = reactApplicationContext.applicationContext, + operationSystemName = operationSystemName, + operationBodyJson = operationBody, + onSuccess = { response -> promise.resolve(response) }, + onError = { error -> promise.resolve(error.toJson()) } + ) + } + + override fun onPushClickedIsRegistered(isRegistered: Boolean) { + MindboxJsDelivery.Shared.hasListeners = isRegistered + } + + override fun setLogLevel(level: Double) { + val logLevel: Level = Level.values()[level.toInt()] + Mindbox.setLogLevel(logLevel) + } + + override fun getSdkVersion(promise: Promise) { + try { + promise.resolve(Mindbox.getSdkVersion()) + } catch (error: Throwable) { + promise.reject(error) } - } - } - Mindbox.registerInAppCallback(ComposableInAppCallback(cb)) - } - - @ReactMethod - fun getDeviceUUID(promise: Promise) { - try { - if (this.deviceUuidSubscription != null) { - Mindbox.disposeDeviceUuidSubscription(this.deviceUuidSubscription!!) - } - - this.deviceUuidSubscription = Mindbox.subscribeDeviceUuid { - deviceUUID -> promise.resolve(deviceUUID) - } - } catch (error: Throwable) { - promise.reject(error) - } - } - - @ReactMethod - fun getFMSToken(promise: Promise) { - try { - if (this.fmsTokenSubscription != null) { - Mindbox.disposePushTokenSubscription(this.fmsTokenSubscription!!) - } - - this.fmsTokenSubscription = Mindbox.subscribePushToken { - fmsToken -> promise.resolve(fmsToken) - } - } catch (error: Throwable) { - promise.reject(error) - } - } - - @ReactMethod - fun getTokens(promise: Promise) { - try { - if (this.getTokensSubscription != null) { - Mindbox.disposePushTokenSubscription(this.getTokensSubscription!!) - } - - this.getTokensSubscription = Mindbox.subscribePushTokens { - tokens -> promise.resolve(tokens) - } - } catch (error: Throwable) { - promise.reject(error) - } - } - - @ReactMethod - fun updateFMSToken(token: String, promise: Promise) { - try { - //It's stub. Used because deprecate updateToken on RN sdk - Mindbox.updateNotificationPermissionStatus(reactApplicationContext.applicationContext) - promise.resolve(true) - } catch (error: Throwable) { - promise.reject(error) - } - } - - @ReactMethod - fun executeAsyncOperation(operationSystemName: String, operationBody: String, promise: Promise) { - Mindbox.executeAsyncOperation(reactApplicationContext.applicationContext, operationSystemName, operationBody) - promise.resolve(true) - } - - @ReactMethod - fun executeSyncOperation(operationSystemName: String, operationBody: String, promise: Promise) { - Mindbox.executeSyncOperation( - context = reactApplicationContext.applicationContext, - operationSystemName = operationSystemName, - operationBodyJson = operationBody, - onSuccess = { - response -> promise.resolve(response) - }, - onError = { - error -> promise.resolve(error.toJson()) - } - ) - } - - @ReactMethod - fun onPushClickedIsRegistered(isRegistered: Boolean) { - MindboxJsDelivery.Shared.hasListeners = isRegistered - } - - @ReactMethod - fun setLogLevel(level: Int) { - val logLevel : Level = Level.values()[level] - Mindbox.setLogLevel(logLevel) - } - - @ReactMethod - fun getSdkVersion(promise: Promise) { - try { - promise.resolve(Mindbox.getSdkVersion()) - } catch (error: Throwable) { - promise.reject(error) - } - } - - @ReactMethod - fun pushDelivered(uniqKey: String) { - Mindbox.onPushReceived( - context = reactApplicationContext.applicationContext, - uniqKey = uniqKey, - ) - } - - @ReactMethod - fun refreshNotificationPermissionStatus() { - Mindbox.updateNotificationPermissionStatus( - context = reactApplicationContext.applicationContext, - ) - } - - @ReactMethod - fun writeNativeLog(message: String, logLevel: Int) { - val logLevel : Level = Level.values()[logLevel] - Mindbox.writeLog(message, logLevel) - } + } + + override fun pushDelivered(uniqKey: String) { + Mindbox.onPushReceived( + context = reactApplicationContext.applicationContext, + uniqKey = uniqKey, + ) + } + + override fun refreshNotificationPermissionStatus() { + Mindbox.updateNotificationPermissionStatus( + context = reactApplicationContext.applicationContext, + ) + } + + override fun writeNativeLog(message: String, logLevel: Double) { + val level: Level = Level.values()[logLevel.toInt()] + Mindbox.writeLog(message, level) + } + + override fun invalidate() { + clearActiveModule(this) + super.invalidate() + } } diff --git a/android/src/main/java/com/mindboxsdk/MindboxSdkPackage.kt b/android/src/main/java/com/mindboxsdk/MindboxSdkPackage.kt index 148b1c1..e29c2e8 100644 --- a/android/src/main/java/com/mindboxsdk/MindboxSdkPackage.kt +++ b/android/src/main/java/com/mindboxsdk/MindboxSdkPackage.kt @@ -1,17 +1,33 @@ package com.mindboxsdk -import com.facebook.react.ReactPackage +import com.facebook.react.TurboReactPackage import com.facebook.react.bridge.NativeModule import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.module.model.ReactModuleInfo +import com.facebook.react.module.model.ReactModuleInfoProvider +import com.facebook.react.turbomodule.core.interfaces.TurboModule import com.facebook.react.uimanager.ViewManager +class MindboxSdkPackage : TurboReactPackage() { + override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? = + if (name == NativeMindboxSdkSpec.NAME) MindboxSdkModule(reactContext) else null -class MindboxSdkPackage : ReactPackage { - override fun createNativeModules(reactContext: ReactApplicationContext): List { - return listOf(MindboxSdkModule(reactContext)) + override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider { + mapOf( + NativeMindboxSdkSpec.NAME to ReactModuleInfo( + NativeMindboxSdkSpec.NAME, + MindboxSdkModule::class.java.name, + false, + false, + false, + TurboModule::class.java.isAssignableFrom(MindboxSdkModule::class.java) + ) + ) } - override fun createViewManagers(reactContext: ReactApplicationContext): List> { - return emptyList() - } + override fun createNativeModules(reactContext: ReactApplicationContext): List = + emptyList() + + override fun createViewManagers(reactContext: ReactApplicationContext): List> = + emptyList() } diff --git a/babel.config.js b/babel.config.js index cf1f9fb..3e0218e 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,3 +1,3 @@ module.exports = { - presets: ['module:metro-react-native-babel-preset'], + presets: ['module:@react-native/babel-preset'], } diff --git a/example/exampleApp/android/app/build.gradle b/example/exampleApp/android/app/build.gradle index c9adc89..f5a2b19 100644 --- a/example/exampleApp/android/app/build.gradle +++ b/example/exampleApp/android/app/build.gradle @@ -1,14 +1,14 @@ apply plugin: "com.android.application" apply plugin: "org.jetbrains.kotlin.android" apply plugin: "com.facebook.react" -apply plugin: 'com.google.gms.google-services' -apply plugin: 'kotlin-android' -apply plugin: 'com.huawei.agconnect' - +apply plugin: "com.google.gms.google-services" +apply plugin: "kotlin-android" +apply plugin: "com.huawei.agconnect" react { - + autolinkLibrariesWithApp() } + def enableProguardInReleaseBuilds = false def jscFlavor = 'org.webkit:android-jsc:+' @@ -47,9 +47,6 @@ android { dependencies { implementation("com.facebook.react:react-android") - implementation "com.facebook.react:react-native:0.74.0" - - // Integration of Mindbox SDK and necessary Firebase and Huawei services for mobile push notifications functionality implementation platform('com.google.firebase:firebase-bom:29.3.1') implementation 'com.google.firebase:firebase-analytics-ktx' implementation 'com.google.firebase:firebase-messaging-ktx' @@ -58,8 +55,6 @@ dependencies { implementation 'com.huawei.hms:push:6.7.0.300' implementation 'cloud.mindbox:mindbox-rustore' implementation 'ru.rustore.sdk:pushclient:6.5.1' - - //gson implementation 'com.google.code.gson:gson:2.8.8' if (hermesEnabled.toBoolean()) { @@ -68,6 +63,3 @@ dependencies { implementation jscFlavor } } - -apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) - diff --git a/example/exampleApp/android/app/src/main/java/com/exampleapp/MainActivity.kt b/example/exampleApp/android/app/src/main/java/com/exampleapp/MainActivity.kt index 790922f..6c212ef 100644 --- a/example/exampleApp/android/app/src/main/java/com/exampleapp/MainActivity.kt +++ b/example/exampleApp/android/app/src/main/java/com/exampleapp/MainActivity.kt @@ -4,8 +4,10 @@ import android.content.Context import android.content.Intent import android.os.Bundle import cloud.mindbox.mobile_sdk.Mindbox +import cloud.mindbox.mobile_sdk.logger.Level import com.facebook.react.ReactActivity import com.facebook.react.ReactActivityDelegate +import com.facebook.react.ReactHost import com.facebook.react.ReactInstanceManager import com.facebook.react.bridge.ReactContext import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled @@ -13,56 +15,70 @@ import com.facebook.react.defaults.DefaultReactActivityDelegate import com.mindboxsdk.MindboxJsDelivery class MainActivity : ReactActivity() { + private var jsDelivery: MindboxJsDelivery? = null - override fun getMainComponentName(): String = "exampleApp" - override fun createReactActivityDelegate(): ReactActivityDelegate = - DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) + private fun sendIntent(context: Context, intent: Intent) { + Mindbox.onNewIntent(intent) + Mindbox.onPushClicked(context, intent) + jsDelivery?.sendPushClicked(intent) + } - // Initializes MindboxJsDelivery and sends the current intent to React Native - // https://developers.mindbox.ru/docs/flutter-push-navigation-react-native private fun initializeAndSentIntent(context: ReactContext) { jsDelivery = MindboxJsDelivery.Shared.getInstance(context) + if (context.hasCurrentActivity()) { - sendIntent(context, context.getCurrentActivity()!!.getIntent()) + sendIntent(context, context.currentActivity!!.intent) } else { sendIntent(context, this.getIntent()) } } + override fun getMainComponentName(): String = "exampleApp" + + override fun createReactActivityDelegate(): ReactActivityDelegate = + DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val reactInstanceManager = getReactNativeHost().getReactInstanceManager(); - val reactContext = reactInstanceManager.getCurrentReactContext(); - - // Initialize and send intent if React context is already available - // https://developers.mindbox.ru/docs/flutter-push-navigation-react-native - if (reactContext != null) { - initializeAndSentIntent(reactContext); + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + Mindbox.writeLog("[RN][exampleApp] New arch enabled", Level.DEBUG) + val reactHost: ReactHost = reactActivityDelegate.reactHost + val context: ReactContext? = reactHost.currentReactContext as? ReactContext + if (context != null) { + Mindbox.writeLog("[RN][exampleApp] ReactContext available on start", Level.DEBUG) + initializeAndSentIntent(context) + } else { + reactHost.addReactInstanceEventListener(object : + ReactInstanceManager.ReactInstanceEventListener { + override fun onReactContextInitialized(context: ReactContext) { + Mindbox.writeLog("[RN][exampleApp] ReactContext available on listener", Level.DEBUG) + initializeAndSentIntent(context) + reactHost.removeReactInstanceEventListener(this) + } + }) + } } else { - // Add listener to initialize and send intent once React context is initialized - reactInstanceManager.addReactInstanceEventListener(object : - ReactInstanceManager.ReactInstanceEventListener { - override fun onReactContextInitialized(context: ReactContext) { - initializeAndSentIntent(context) - reactInstanceManager.removeReactInstanceEventListener(this) - } - }) + Mindbox.writeLog("[RN][exampleApp] Old architecture", Level.DEBUG) + val reactInstanceManager: ReactInstanceManager = getReactNativeHost().reactInstanceManager + val reactContext: ReactContext? = reactInstanceManager.getCurrentReactContext() + if (reactContext != null) { + initializeAndSentIntent(reactContext) + Mindbox.writeLog("[RN][exampleApp] ReactContext available on start", Level.DEBUG) + } else { + reactInstanceManager.addReactInstanceEventListener(object : + ReactInstanceManager.ReactInstanceEventListener { + override fun onReactContextInitialized(context: ReactContext) { + initializeAndSentIntent(context) + reactInstanceManager.removeReactInstanceEventListener(this) + } + }) + } } } - // Handles new intents received by the activity override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) sendIntent(this, intent) } - - // Sends the received intent to Mindbox and React Native - // https://developers.mindbox.ru/docs/android-get-click-react-native - private fun sendIntent(context: Context, intent: Intent) { - Mindbox.onNewIntent(intent) - //send click action - Mindbox.onPushClicked(context, intent) - jsDelivery?.sendPushClicked(intent); - } } diff --git a/example/exampleApp/android/app/src/main/java/com/exampleapp/MainApplication.kt b/example/exampleApp/android/app/src/main/java/com/exampleapp/MainApplication.kt index 7436ac3..fa43b14 100644 --- a/example/exampleApp/android/app/src/main/java/com/exampleapp/MainApplication.kt +++ b/example/exampleApp/android/app/src/main/java/com/exampleapp/MainApplication.kt @@ -10,69 +10,76 @@ import cloud.mindbox.mobile_sdk.pushes.MindboxRemoteMessage import cloud.mindbox.mindbox_firebase.MindboxFirebase import cloud.mindbox.mindbox_huawei.MindboxHuawei import cloud.mindbox.mobile_sdk.Mindbox +import com.exampleapp.NotificationPackage import com.facebook.react.PackageList import com.facebook.react.ReactApplication -import com.facebook.react.ReactInstanceManager -import com.facebook.react.ReactNativeHost +import com.facebook.react.ReactHost import com.facebook.react.ReactPackage -import com.facebook.soloader.SoLoader -import com.exampleapp.NotificationPackage +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load +import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost +import com.facebook.react.defaults.DefaultReactNativeHost import com.facebook.react.modules.core.DeviceEventManagerModule +import com.facebook.react.soloader.OpenSourceMergedSoMapping +import com.facebook.soloader.SoLoader import com.google.gson.Gson import com.google.gson.reflect.TypeToken import cloud.mindbox.mindbox_rustore.MindboxRuStore -import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load -import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost -import com.facebook.react.defaults.DefaultReactNativeHost class MainApplication : Application(), ReactApplication { - override val reactNativeHost: ReactNativeHost = - object : ReactNativeHost(this) { - override fun getPackages(): List = - PackageList(this).packages.apply { - add(NotificationPackage()) - } - override fun getJSMainModuleName(): String = "index" - override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG + override val reactNativeHost: DefaultReactNativeHost = + object : DefaultReactNativeHost(this) { + override fun getPackages(): List = + PackageList(this).packages.apply { + add(NotificationPackage()) } - override fun onCreate() { - super.onCreate() - //The fifth step of https://developers.mindbox.ru/docs/firebase-send-push-notifications-react-native - Mindbox.initPushServices(this, listOf(MindboxFirebase, MindboxHuawei, MindboxRuStore)) - SoLoader.init(this, false) - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { - // If you opted-in for the New Architecture, we load the native entry point for this app. - load() - } + override fun getJSMainModuleName(): String = "index" + + override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG + + override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED + + override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED } - private val gson = Gson() + override val reactHost: ReactHost + get() = getDefaultReactHost(applicationContext, reactNativeHost) - fun saveNotification(message: MindboxRemoteMessage) { - val sharedPreferences = getSharedPreferences("notifications", Context.MODE_PRIVATE) - val editor = sharedPreferences.edit() - val notificationsJson = sharedPreferences.getString("notifications", "[]") - val type = object : TypeToken>() {}.type - val notifications: MutableList = gson.fromJson(notificationsJson, type) - notifications.add(gson.toJson(message)) - editor.putString("notifications", gson.toJson(notifications)) - editor.apply() - notifyJS() + override fun onCreate() { + super.onCreate() + Mindbox.initPushServices(this, listOf(MindboxFirebase, MindboxHuawei, MindboxRuStore)) + SoLoader.init(this, OpenSourceMergedSoMapping) + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + load() } + } - private fun notifyJS() { - val handler = Handler(Looper.getMainLooper()) - handler.post { - try { - val reactInstanceManager = reactNativeHost.reactInstanceManager - reactInstanceManager.currentReactContext - ?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) - ?.emit("newNotification", null) - } catch (e: Exception) { - Log.e("MainApplication", "Error notifying React Native", e) - } - } + private val gson = Gson() + + fun saveNotification(message: MindboxRemoteMessage) { + val sharedPreferences = getSharedPreferences("notifications", Context.MODE_PRIVATE) + val editor = sharedPreferences.edit() + val notificationsJson = sharedPreferences.getString("notifications", "[]") + val type = object : TypeToken>() {}.type + val notifications: MutableList = gson.fromJson(notificationsJson, type) + notifications.add(gson.toJson(message)) + editor.putString("notifications", gson.toJson(notifications)) + editor.apply() + notifyJS() + } + + private fun notifyJS() { + val handler = Handler(Looper.getMainLooper()) + handler.post { + try { + val reactInstanceManager = reactNativeHost.reactInstanceManager + reactInstanceManager.currentReactContext + ?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) + ?.emit("newNotification", null) + } catch (e: Exception) { + Log.e("MainApplication", "Error notifying React Native", e) + } } + } } diff --git a/example/exampleApp/android/build.gradle b/example/exampleApp/android/build.gradle index d2f118c..2bcf733 100644 --- a/example/exampleApp/android/build.gradle +++ b/example/exampleApp/android/build.gradle @@ -1,46 +1,43 @@ buildscript { ext { - buildToolsVersion = "34.0.0" - minSdkVersion = 23 + buildToolsVersion = "35.0.0" + minSdkVersion = 24 compileSdkVersion = 35 targetSdkVersion = 35 ndkVersion = "26.1.10909125" - kotlinVersion = "1.8.0" - cmakeVersion = "3.22.1" + kotlinVersion = "1.9.24" } repositories { google() mavenCentral() maven { url 'https://developer.huawei.com/repo/' } - maven {url 'https://artifactory-external.vkpartner.ru/artifactory/maven'} + maven { url 'https://artifactory-external.vkpartner.ru/artifactory/maven' } } dependencies { classpath("com.android.tools.build:gradle:8.6.0") - classpath("com.facebook.react:react-native-gradle-plugin:7.5") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") - classpath 'com.google.gms:google-services:4.3.14' - classpath "com.huawei.agconnect:agcp:1.8.0.300" + classpath("com.facebook.react:react-native-gradle-plugin") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") + classpath("com.google.gms:google-services:4.3.14") + classpath("com.huawei.agconnect:agcp:1.8.0.300") } } + allprojects { repositories { mavenCentral() mavenLocal() maven { - // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm url("$rootDir/../node_modules/react-native/android") } maven { - // Android JSC is installed from npm url("$rootDir/../node_modules/jsc-android/dist") } google() - maven { url 'https://www.jitpack.io' } + maven { url "https://www.jitpack.io" } maven { url "https://plugins.gradle.org/m2/" } - maven { url 'https://developer.huawei.com/repo/' } - maven {url 'https://artifactory-external.vkpartner.ru/artifactory/maven'} + maven { url "https://developer.huawei.com/repo/" } + maven { url "https://artifactory-external.vkpartner.ru/artifactory/maven" } } - } apply plugin: "com.facebook.react.rootproject" diff --git a/example/exampleApp/android/gradle.properties b/example/exampleApp/android/gradle.properties index 1351076..6a9a7a2 100644 --- a/example/exampleApp/android/gradle.properties +++ b/example/exampleApp/android/gradle.properties @@ -26,7 +26,7 @@ android.enableJetifier=true # Use this property to specify which architecture you want to build. # You can also override it from the CLI using -# ./gradlew -PreactNativeArchitectures=x86_64 +# ./gradlew -PreactNativeArchitectures=x86_64 reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 # Use this property to enable support to the new architecture. @@ -34,7 +34,7 @@ reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 # your application. You should enable this flag either if you want # to write custom TurboModules/Fabric components OR use libraries that # are providing them. -newArchEnabled=false +newArchEnabled=true # Use this property to enable or disable the Hermes JS engine. # If set to false, you will be using JSC instead. diff --git a/example/exampleApp/android/gradle/wrapper/gradle-wrapper.properties b/example/exampleApp/android/gradle/wrapper/gradle-wrapper.properties index e7646de..79eb9d0 100644 --- a/example/exampleApp/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/exampleApp/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/example/exampleApp/android/settings.gradle b/example/exampleApp/android/settings.gradle index e9ce508..b27a69e 100644 --- a/example/exampleApp/android/settings.gradle +++ b/example/exampleApp/android/settings.gradle @@ -1,4 +1,15 @@ -rootProject.name = 'exampleApp' -apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) -include ':app' -includeBuild('../node_modules/@react-native/gradle-plugin') +pluginManagement { + includeBuild("../node_modules/@react-native/gradle-plugin") +} + +plugins { + id("com.facebook.react.settings") +} + +extensions.configure(com.facebook.react.ReactSettingsExtension) { ex -> + ex.autolinkLibrariesFromCommand() +} + +rootProject.name = "exampleApp" +include(":app") +includeBuild("../node_modules/@react-native/gradle-plugin") diff --git a/example/exampleApp/ios/Podfile b/example/exampleApp/ios/Podfile index b70ff6c..80262f4 100644 --- a/example/exampleApp/ios/Podfile +++ b/example/exampleApp/ios/Podfile @@ -1,14 +1,14 @@ -def node_require(script) - # Resolve script with node to allow for hoisting - require Pod::Executable.execute_command('node', ['-p', - "require.resolve( - '#{script}', - {paths: [process.argv[1]]}, - )", __dir__]).strip -end - -node_require('react-native/scripts/react_native_pods.rb') -node_require('react-native-permissions/scripts/setup.rb') +require Pod::Executable.execute_command('node', ['-p', + 'require.resolve( + "react-native/scripts/react_native_pods.rb", + {paths: [process.argv[1]]}, + )', __dir__]).strip + +require Pod::Executable.execute_command('node', ['-p', + 'require.resolve( + "react-native-permissions/scripts/setup.rb", + {paths: [process.argv[1]]}, + )', __dir__]).strip platform :ios, min_ios_version_supported prepare_react_native_project! @@ -26,26 +26,25 @@ target 'exampleApp' do use_react_native!( :path => config[:reactNativePath], - # An absolute path to your application root. :app_path => "#{Pod::Config.instance.installation_root}/.." ) pod 'Mindbox' -target 'MindboxNotificationServiceExtension' do - pod 'MindboxNotifications' + target 'MindboxNotificationServiceExtension' do + pod 'MindboxNotifications' end -target 'MindboxNotificationContentExtension' do - pod 'MindboxNotifications' + target 'MindboxNotificationContentExtension' do + pod 'MindboxNotifications' end post_install do |installer| installer.pods_project.targets.each do |target| - target.build_configurations.each do |config| - config.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'No' - end - end + target.build_configurations.each do |buildConfig| + buildConfig.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'No' + end + end react_native_post_install( installer, config[:reactNativePath], @@ -53,4 +52,3 @@ target 'MindboxNotificationContentExtension' do ) end end - diff --git a/example/exampleApp/ios/exampleApp.xcodeproj/project.pbxproj b/example/exampleApp/ios/exampleApp.xcodeproj/project.pbxproj index a267e63..83002a2 100644 --- a/example/exampleApp/ios/exampleApp.xcodeproj/project.pbxproj +++ b/example/exampleApp/ios/exampleApp.xcodeproj/project.pbxproj @@ -585,6 +585,7 @@ DEVELOPMENT_TEAM = 622436AMYX; ENABLE_BITCODE = NO; INFOPLIST_FILE = exampleApp/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -619,6 +620,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 622436AMYX; INFOPLIST_FILE = exampleApp/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -663,7 +665,7 @@ INFOPLIST_FILE = MindboxNotificationServiceExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = MindboxNotificationServiceExtension; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 13.4; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -710,7 +712,7 @@ INFOPLIST_FILE = MindboxNotificationServiceExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = MindboxNotificationServiceExtension; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 13.4; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -754,7 +756,7 @@ INFOPLIST_FILE = MindboxNotificationContentExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = MindboxNotificationContentExtension; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 13.4; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -800,7 +802,7 @@ INFOPLIST_FILE = MindboxNotificationContentExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = MindboxNotificationContentExtension; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 13.4; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -898,7 +900,7 @@ ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; USE_HERMES = true; }; name = Debug; diff --git a/example/exampleApp/ios/exampleApp/PrivacyInfo.xcprivacy b/example/exampleApp/ios/exampleApp/PrivacyInfo.xcprivacy index 01ce452..2d04442 100644 --- a/example/exampleApp/ios/exampleApp/PrivacyInfo.xcprivacy +++ b/example/exampleApp/ios/exampleApp/PrivacyInfo.xcprivacy @@ -2,10 +2,33 @@ - NSPrivacyTracking - - NSPrivacyTrackingDomains - + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 35F9.1 + + + NSPrivacyCollectedDataTypes @@ -13,36 +36,29 @@ NSPrivacyCollectedDataTypeOtherDiagnosticData NSPrivacyCollectedDataTypeLinked - NSPrivacyCollectedDataTypeTracking - NSPrivacyCollectedDataTypePurposes NSPrivacyCollectedDataTypePurposeAppFunctionality + NSPrivacyCollectedDataTypeTracking + NSPrivacyCollectedDataType NSPrivacyCollectedDataTypeDeviceID NSPrivacyCollectedDataTypeLinked - NSPrivacyCollectedDataTypeTracking - NSPrivacyCollectedDataTypePurposes NSPrivacyCollectedDataTypePurposeProductPersonalization + NSPrivacyCollectedDataTypeTracking + - NSPrivacyAccessedAPITypes - - - NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategoryUserDefaults - NSPrivacyAccessedAPITypeReasons - - CA92.1 - - - + NSPrivacyTracking + + NSPrivacyTrackingDomains + diff --git a/example/exampleApp/package.json b/example/exampleApp/package.json index a755306..f8c11e4 100644 --- a/example/exampleApp/package.json +++ b/example/exampleApp/package.json @@ -12,26 +12,23 @@ "test": "jest" }, "dependencies": { - "@react-navigation/native": "^6.1.6", - "@react-navigation/native-stack": "^6.9.17", - "@react-navigation/stack": "^6.3.20", - "mindbox-sdk": "^2.13.1", - "react": "18.2.0", - "react-native": "0.74.0", - "react-native-gesture-handler": "2.21.2", + "mindbox-sdk": "^2.15.0", + "react": "18.3.1", + "react-native": "0.76.0", "react-native-permissions": "^5.4.0", - "react-native-safe-area-context": "^4.9.0", - "react-native-screens": "^3.29.0", "react-native-snackbar": "^2.8.0" }, "devDependencies": { - "@babel/core": "^7.20.0", - "@babel/preset-env": "^7.20.0", - "@babel/runtime": "^7.20.0", - "@react-native/babel-preset": "0.74.0", - "@react-native/eslint-config": "0.74.0", - "@react-native/metro-config": "0.74.0", - "@react-native/typescript-config": "0.74.0", + "@babel/core": "^7.25.2", + "@babel/preset-env": "^7.25.3", + "@babel/runtime": "^7.25.0", + "@react-native-community/cli": "15.0.0", + "@react-native-community/cli-platform-android": "15.0.0", + "@react-native-community/cli-platform-ios": "15.0.0", + "@react-native/babel-preset": "0.76.0", + "@react-native/eslint-config": "0.76.0", + "@react-native/metro-config": "0.76.0", + "@react-native/typescript-config": "0.76.0", "@types/react": "^18.2.6", "@types/react-test-renderer": "^18.0.0", "babel-jest": "^29.6.3", @@ -39,7 +36,7 @@ "husky": "^9.0.10", "jest": "^29.6.3", "prettier": "2.8.8", - "react-test-renderer": "18.2.0", + "react-test-renderer": "18.3.1", "typescript": "5.0.4" }, "engines": { diff --git a/example/exampleApp/src/App.tsx b/example/exampleApp/src/App.tsx index 5330f4d..3f4caf0 100644 --- a/example/exampleApp/src/App.tsx +++ b/example/exampleApp/src/App.tsx @@ -1,21 +1,28 @@ -import React from 'react' -import { NavigationContainer } from '@react-navigation/native' -import { createNativeStackNavigator } from '@react-navigation/native-stack' +import React, { useMemo, useState } from 'react' +import { AppNavigationContext, RouteName } from './navigation/AppNavigationContext' import HomeScreen from './screens/HomeScreen' import PushNotificationScreen from './screens/PushNotificationScreen' import NotificationCenterScreen from './screens/NotificationCenterScreen' -const Stack = createNativeStackNavigator() - function App() { + const [route, setRoute] = useState('Home') + const navigation = useMemo( + () => ({ + navigate: (name: RouteName) => { + setRoute(name) + }, + goBack: () => { + setRoute('Home') + }, + }), + [] + ) return ( - - - - - - - + + {route === 'Home' ? : null} + {route === 'PushNotification' ? : null} + {route === 'NotificationCenter' ? : null} + ) } diff --git a/example/exampleApp/src/navigation/AppNavigationContext.tsx b/example/exampleApp/src/navigation/AppNavigationContext.tsx new file mode 100644 index 0000000..f73ba1b --- /dev/null +++ b/example/exampleApp/src/navigation/AppNavigationContext.tsx @@ -0,0 +1,20 @@ +import { createContext, useContext } from 'react' + +export type RouteName = 'Home' | 'PushNotification' | 'NotificationCenter' + +export type AppNavigation = { + navigate(name: RouteName): void + goBack(): void +} + +const AppNavigationContext = createContext(null) + +export function useAppNavigation(): AppNavigation { + const value = useContext(AppNavigationContext) + if (value == null) { + throw new Error('useAppNavigation must be used within AppNavigationContext.Provider') + } + return value +} + +export { AppNavigationContext } diff --git a/example/exampleApp/src/screens/HomeScreen.tsx b/example/exampleApp/src/screens/HomeScreen.tsx index 17c52e4..ebb9573 100644 --- a/example/exampleApp/src/screens/HomeScreen.tsx +++ b/example/exampleApp/src/screens/HomeScreen.tsx @@ -3,8 +3,7 @@ import { SafeAreaView, StyleSheet, Text, View, Platform, Button } from 'react-na import MindboxSdk, { LogLevel, CopyPayloadInAppCallback, EmptyInAppCallback, InAppCallback, UrlInAppCallback } from 'mindbox-sdk' import { sendSync, sendAsync, asyncOperationNCOpen } from '../utils/MindboxOperations' import { requestNotificationPermission } from '../utils/RequestPermission' -import PushNotificationScreen from './screens/PushNotificationScreen' -import { useNavigation } from '@react-navigation/native' +import { useAppNavigation } from '../navigation/AppNavigationContext' import { chooseInappCallback, RegisterInappCallback } from '../utils/InAppCallbacks' const configuration = { @@ -16,7 +15,7 @@ const configuration = { } const HomeScreen = () => { - const navigation = useNavigation() + const navigation = useAppNavigation() const [deviceUUID, setDeviceUUID] = useState('Empty') const [token, setToken] = useState('Empty') const [pushData, setPushData] = useState({ @@ -25,6 +24,35 @@ const HomeScreen = () => { }) const [sdkVersion, setSdkVersion] = useState('Empty') + const appInitializationCallback = useCallback(async () => { + try { + // https://developers.mindbox.ru/docs/%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D1%8B-react-natice-sdk#mindboxinitialize + await MindboxSdk.initialize(configuration) + } catch (error) { + console.log(error) + } + }, []) + + const navigateToPushNotificationIfRequired = useCallback( + (pushUrl: string | null) => { + if (pushUrl != null && pushUrl.includes('gotoanotherscreen')) { + navigation.navigate('PushNotification') + } + }, + [navigation] + ) + + const getPushData = useCallback( + (pushUrl: string | null, pushPayload: string | null) => { + setTimeout(() => { + // https://developers.mindbox.ru/docs/flutter-push-navigation-react-native + navigateToPushNotificationIfRequired(pushUrl) + setPushData({ pushUrl, pushPayload }) + }, 600) + }, + [navigateToPushNotificationIfRequired] + ) + useEffect(() => { // https://developers.mindbox.ru/docs/%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D1%8B-react-natice-sdk#setloglevel-since-280 MindboxSdk.setLogLevel(LogLevel.DEBUG) @@ -51,26 +79,6 @@ const HomeScreen = () => { chooseInappCallback(RegisterInappCallback.DEFAULT) }, [appInitializationCallback]) - const appInitializationCallback = useCallback(async () => { - try { - // https://developers.mindbox.ru/docs/%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D1%8B-react-natice-sdk#mindboxinitialize - await MindboxSdk.initialize(configuration) - } catch (error) { - console.log(error) - } - }, []) - - const getPushData = useCallback( - (pushUrl: String | null, pushPayload: String | null) => { - setTimeout(() => { - // https://developers.mindbox.ru/docs/flutter-push-navigation-react-native - navigateToPushNotificationIfRequired(pushUrl) - setPushData({ pushUrl, pushPayload }) - }, 600) - }, - [navigateToPushNotificationIfRequired] - ) - useEffect(() => { // https://developers.mindbox.ru/docs/%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D1%8B-react-natice-sdk#onpushclickreceived MindboxSdk.onPushClickReceived(getPushData) @@ -88,15 +96,6 @@ const HomeScreen = () => { asyncOperationNCOpen() navigation.navigate('NotificationCenter') } - - const navigateToPushNotificationIfRequired = useCallback( - (pushUrl) => { - if (pushUrl && pushUrl.includes('gotoanotherscreen')) { - navigation.navigate('PushNotification') - } - }, - [navigation] - ) return ( diff --git a/example/exampleApp/src/screens/NotificationCenterScreen.tsx b/example/exampleApp/src/screens/NotificationCenterScreen.tsx index 4abc0ab..f65a4bd 100644 --- a/example/exampleApp/src/screens/NotificationCenterScreen.tsx +++ b/example/exampleApp/src/screens/NotificationCenterScreen.tsx @@ -6,11 +6,13 @@ import { asyncOperationNCPushOpen } from '../utils/MindboxOperations' import initialNotifications from '../utils/NotificationStub' import styles from '../components/NotificationScreenStyles' import { Notification } from '../utils/Notification' +import { useAppNavigation } from '../navigation/AppNavigationContext' const { NotificationModule } = NativeModules const notificationEmitter = new NativeEventEmitter(NotificationModule) -const NotificationCenterScreen = ({ navigation }: { navigation: any }) => { +const NotificationCenterScreen = () => { + const navigation = useAppNavigation() const [notifications, setNotifications] = useState([]) useEffect(() => { diff --git a/example/exampleApp/src/screens/PushNotificationScreen.tsx b/example/exampleApp/src/screens/PushNotificationScreen.tsx index 13297a0..9b94a66 100644 --- a/example/exampleApp/src/screens/PushNotificationScreen.tsx +++ b/example/exampleApp/src/screens/PushNotificationScreen.tsx @@ -1,10 +1,14 @@ import React from 'react' -import { View, Text, StyleSheet } from 'react-native' +import { View, Text, StyleSheet, Button } from 'react-native' +import { useAppNavigation } from '../navigation/AppNavigationContext' const PushNotificationScreen = () => { + const navigation = useAppNavigation() return ( Opened after click on push + +