diff --git a/.github/CI_DOCUMENTATION.md b/.github/CI_DOCUMENTATION.md index da7baeb..2db1090 100644 --- a/.github/CI_DOCUMENTATION.md +++ b/.github/CI_DOCUMENTATION.md @@ -20,8 +20,8 @@ All workflows use standardized tooling versions to ensure consistency: | ---------------- | --------- | ----------------------------- | | **Node.js** | `24` | `.nvmrc` | | **Yarn** | `4.12.0` | `package.json#packageManager` | -| **React Native** | `0.83.4` | `package.json` | -| **React** | `19.2.14` | `package.json` | +| **React Native** | `0.84.1` | `package.json` | +| **React** | `19.2.3` | `package.json` | | **TypeScript** | `5.9.3` | `package.json` | ### Android Tooling @@ -55,7 +55,7 @@ All workflows use standardized tooling versions to ensure consistency: | Tool | Version | Defined In | | ------------------------ | --------------------- | ----------------------- | -| **React Native Harness** | `1.0.0-alpha.21` | `package.json` | +| **React Native Harness** | `1.1.0` | `package.json` | | **Android Emulator** | Pixel_API_35 (API 35) | `rn-harness.config.mjs` | | **iOS Simulator** | iPhone 17 (iOS 26.2) | `rn-harness.config.mjs` | diff --git a/.gitignore b/.gitignore index d8bf499..0e40a34 100644 --- a/.gitignore +++ b/.gitignore @@ -90,3 +90,6 @@ lib/ # Fastlane.swift runner binary **/fastlane/FastlaneRunner + +# Harness logs/crash reports +example/.harness diff --git a/MendixNative.podspec b/MendixNative.podspec index 8ed9239..c234757 100644 --- a/MendixNative.podspec +++ b/MendixNative.podspec @@ -14,11 +14,12 @@ Pod::Spec.new do |s| s.source = { :git => "https://github.com/mendix/mendix-native.git", :tag => "#{s.version}" } s.source_files = "ios/**/*.{h,m,mm,cpp,swift}" - s.public_header_files = "ios/**/*.h" - s.private_header_files = "ios/**/*.h" + s.public_header_files = "ios/Modules/Helper/ReactHostHelper.h" + s.private_header_files = "ios/TurboModules/**/*.h" s.dependency "SSZipArchive" s.dependency "RNCAsyncStorage" + s.dependency "ReactAppDependencyProvider" install_modules_dependencies(s) end diff --git a/android/build.gradle b/android/build.gradle index 2a0193c..b7a6b0f 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -36,8 +36,6 @@ android { } buildFeatures { - dataBinding true - viewBinding true buildConfig true } @@ -88,7 +86,7 @@ dependencies { kapt 'com.github.bumptech.glide:compiler:4.12.0' - api "com.facebook.react:react-android:0.83.4" + api "com.facebook.react:react-android:0.84.1" api project(':op-engineering_op-sqlite') api project(':react-native-async-storage_async-storage') api project(':react-native-gesture-handler') diff --git a/android/src/main/java/com/facebook/react/devsupport/DevInternalSettings.kt b/android/src/main/java/com/facebook/react/devsupport/DevInternalSettings.kt deleted file mode 100644 index ceefb9d..0000000 --- a/android/src/main/java/com/facebook/react/devsupport/DevInternalSettings.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.facebook.react.devsupport - -import com.facebook.react.modules.debug.interfaces.DeveloperSettings -import com.mendix.mendixnative.activity.MendixReactActivity -import com.mendix.mendixnative.util.ReflectionUtils - -fun getDevInternalSettings(activity: MendixReactActivity): DeveloperSettings? = - (activity.currentDevSupportManager as? DevSupportManagerBase)?.let { - return ReflectionUtils.getField(it, "mDevSettings") - } diff --git a/android/src/main/java/com/facebook/react/devsupport/DevSupportManagerHelpers.kt b/android/src/main/java/com/facebook/react/devsupport/DevSupportManagerHelpers.kt deleted file mode 100644 index 7b3dcf4..0000000 --- a/android/src/main/java/com/facebook/react/devsupport/DevSupportManagerHelpers.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.facebook.react.devsupport - -import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener -import com.facebook.react.devsupport.interfaces.DevSupportManager -import com.mendix.mendixnative.util.ReflectionUtils - -fun setBundleDownloadListener( - devSupportManager: DevSupportManager?, - listener: DevBundleDownloadListener -) { - devSupportManager?.apply { - ReflectionUtils.setFieldOfSuperclass( - this, - listener, - "mBundleDownloadListener", - "devBundleDownloadListener" - ) - } -} - -fun overrideDevLoadingViewController( - devSupportManager: DevSupportManager, - devLoadingViewController: DefaultDevLoadingViewImplementation -) { - devSupportManager.apply { - ReflectionUtils.setFieldOfSuperclass( - this, - devLoadingViewController, - "mDevLoadingViewManager", - "devLoadingViewManager" - ) - } -} diff --git a/android/src/main/java/com/mendix/mendixnative/MendixInitializer.kt b/android/src/main/java/com/mendix/mendixnative/MendixInitializer.kt index c4db2f3..5961b53 100644 --- a/android/src/main/java/com/mendix/mendixnative/MendixInitializer.kt +++ b/android/src/main/java/com/mendix/mendixnative/MendixInitializer.kt @@ -1,18 +1,10 @@ package com.mendix.mendixnative import android.app.Activity -import android.view.MotionEvent import com.facebook.react.ReactHost -import com.facebook.react.ReactInstanceEventListener -import com.facebook.react.ReactNativeHost -import com.facebook.react.bridge.ReactContext -import com.facebook.react.common.ShakeDetector import com.facebook.react.devsupport.DevSupportManagerBase -import com.facebook.react.devsupport.attachMendixSupportManagerShakeDetector -import com.facebook.react.devsupport.makeShakeDetector import com.facebook.react.modules.network.OkHttpClientProvider import com.mendix.mendixnative.config.AppPreferences -import com.mendix.mendixnative.handler.DevMenuTouchEventHandler import com.mendix.mendixnative.react.* import com.mendix.mendixnative.request.MendixNetworkInterceptor import com.mendix.mendixnative.util.CookieEncryption @@ -20,25 +12,14 @@ import com.mendix.mendixnative.react.MxConfiguration import com.mendix.mendixnative.react.clearCachedReactNativeDevBundle import com.mendix.mendixnative.react.clearData import com.mendix.mendixnative.react.closeSqlDatabaseConnection -import com.mendix.mendixnative.react.toggleElementInspector -import com.mendixnative.MendixNativeModule class MendixInitializer( private val context: Activity, private val reactHost: ReactHost, - private val reactNativeHost: ReactNativeHost, private val hasRNDeveloperSupport: Boolean = false, -) : ReactInstanceEventListener { - private var shakeDetector: ShakeDetector? = null - private var devMenuTouchEventHandler: DevMenuTouchEventHandler? = null +) { - fun onCreate( - mendixApp: MendixApp, - devAppMenuHandler: DevAppMenuHandler = object : DevAppMenuHandler { - override fun showDevAppMenu() {} - }, - clearData: Boolean, - ) { + fun onCreate(mendixApp: MendixApp, clearData: Boolean) { // Assign mendix xas id interceptor to okhttp CookieEncryption.init(this.context) if (CookieEncryption.isCookieEncryptionEnabled()) { @@ -53,77 +34,25 @@ class MendixInitializer( MxConfiguration.runtimeUrl = runtimeUrl MxConfiguration.warningsFilter = mendixApp.warningsFilter - // This is here to make sure that a clean host instance is initialised. - restartReactInstanceManager() + // Reload only if there's already a running instance. + if (reactHost.currentReactContext != null) { + reactHost.reload("Clean start for new Mendix app") + } if (clearData) clearData(context.application) if (hasRNDeveloperSupport) setupDeveloperApp(runtimeUrl, mendixApp) - if (mendixApp.attachCustomDeveloperMenu) attachCustomDeveloperMenu(devAppMenuHandler) - } - - private fun restartReactInstanceManager() { - if (reactNativeHost.hasInstance()) reactNativeHost.clear() - // Pre-initialize reactInstanceManager to be available for other methods - if (reactNativeHost.hasInstance()) reactNativeHost.reactInstanceManager - } - - private fun attachCustomDeveloperMenu(devAppMenuHandler: DevAppMenuHandler) { - devMenuTouchEventHandler = - DevMenuTouchEventHandler(object : DevMenuTouchEventHandler.DevMenuTouchListener { - override fun onTap() { - reactNativeHost.typeSafeNativeModule()?.reloadClientWithState() - } - - override fun onLongPress() { - devAppMenuHandler.showDevAppMenu() - } - }) - - attachShakeDetector(devAppMenuHandler) } fun onDestroy() { - // Stop shaking as early as possible to avoid orphaned dialogs - stopShakeDetector() - if (hasRNDeveloperSupport) { AppPreferences(context.applicationContext).setElementInspector(false) - reactHost.removeReactInstanceEventListener(this) } - - // We need to clear the host to allow for reinitialization of the Native Modules - // Especially for when switching between apps - reactNativeHost.clear() + // Destroy the current instance but keep the host reusable — invalidate() is terminal + // in bridgeless mode and would prevent the host from ever starting a new instance. + reactHost.destroy("MendixInitializer.onDestroy()", null) // We need to close all databases separately to avoid hitting a read only state exception // Databases need to close after we are done closing the react native host to avoid db locks - closeSqlDatabaseConnection(reactNativeHost.reactApplicationContext()) - } - - fun stopShakeDetector() { - shakeDetector?.stop() - } - - override fun onReactContextInitialized(context: ReactContext) { - val preferences = AppPreferences(context) - if (preferences.isElementInspectorEnabled) { - toggleElementInspector(context) - } - } - - fun dispatchTouchEvent(ev: MotionEvent?): Boolean { - return devMenuTouchEventHandler?.handle(ev) ?: false - } - - private fun attachShakeDetector(devAppMenuHandler: DevAppMenuHandler) { - if (shakeDetector == null) { - shakeDetector = makeShakeDetector(context.applicationContext) { - devAppMenuHandler.showDevAppMenu() - } - } - - (reactHost.devSupportManager as? DevSupportManagerBase)?.run { - attachMendixSupportManagerShakeDetector(shakeDetector!!, this) - } + closeSqlDatabaseConnection(reactHost.currentReactContext) } private fun setupDeveloperApp( @@ -137,7 +66,6 @@ class MendixInitializer( preferences.setDevMode((mendixApp.showExtendedDevMenu)) clearCachedReactNativeDevBundle(context.application) - reactHost.addReactInstanceEventListener(this) } } diff --git a/android/src/main/java/com/mendix/mendixnative/MendixReactApplication.kt b/android/src/main/java/com/mendix/mendixnative/MendixReactApplication.kt index f32c1b3..e412ea5 100644 --- a/android/src/main/java/com/mendix/mendixnative/MendixReactApplication.kt +++ b/android/src/main/java/com/mendix/mendixnative/MendixReactApplication.kt @@ -10,6 +10,7 @@ import com.facebook.react.common.annotations.UnstableReactNativeAPI import com.facebook.react.defaults.DefaultComponentsRegistry import com.facebook.react.defaults.DefaultReactHostDelegate import com.facebook.react.defaults.DefaultTurboModuleManagerDelegate +import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener import com.facebook.react.devsupport.interfaces.RedBoxHandler import com.facebook.react.fabric.ComponentFactory import com.facebook.react.runtime.ReactHostImpl @@ -20,6 +21,7 @@ import com.mendix.mendixnative.error.ErrorHandler import com.mendix.mendixnative.error.ErrorHandlerFactory import com.mendix.mendixnative.error.mapErrorHandlerToRedBox import com.mendix.mendixnative.handler.DummyErrorHandler +import com.mendix.mendixnative.devsupport.MendixDevSupportManagerFactory import com.mendix.mendixnative.react.ota.OtaJSBundleUrlProvider import com.mendix.mendixnative.react.splash.MendixSplashScreenPresenter import com.mendixnative.MendixNativePackage @@ -106,11 +108,12 @@ abstract class MendixReactApplication : Application(), MendixApplication, ErrorH val componentFactory = ComponentFactory() DefaultComponentsRegistry.register(componentFactory) ReactHostImpl( - applicationContext, - delegate, - componentFactory, - true /* allowPackagerServerAccess */, - useDeveloperSupport, + context = applicationContext, + reactHostDelegate = delegate, + componentFactory = componentFactory, + allowPackagerServerAccess = true, + useDevSupport = useDeveloperSupport, + devSupportManagerFactory = MendixDevSupportManagerFactory(devBundleDownloadListener), ) } @@ -158,4 +161,14 @@ abstract class MendixReactApplication : Application(), MendixApplication, ErrorH open val jsBundleProvider: JSBundleFileProvider? get() = null + + /** + * Override this to provide a [DevBundleDownloadListener] that receives bundle download + * progress, success, and failure callbacks. This is used to drive custom loading UIs. + * + * The listener is injected at [ReactHost] creation time via [MendixDevSupportManagerFactory]. + * Use a delegating holder pattern if the actual listener is created after app initialization. + */ + open val devBundleDownloadListener: DevBundleDownloadListener? + get() = null } diff --git a/android/src/main/java/com/mendix/mendixnative/activity/MendixReactActivity.kt b/android/src/main/java/com/mendix/mendixnative/activity/MendixReactActivity.kt index 1e2b04a..6b2130e 100644 --- a/android/src/main/java/com/mendix/mendixnative/activity/MendixReactActivity.kt +++ b/android/src/main/java/com/mendix/mendixnative/activity/MendixReactActivity.kt @@ -2,7 +2,6 @@ package com.mendix.mendixnative.activity import android.os.Bundle import android.view.KeyEvent -import android.view.MotionEvent import com.facebook.react.ReactActivity import com.facebook.react.ReactActivityDelegate import com.facebook.react.bridge.ReactContext @@ -31,8 +30,8 @@ open class MendixReactActivity : ReactActivity(), DevAppMenuHandler, LaunchScree ?: throw ClassCastException("Application needs to implement MendixApplication") mendixInitializer = - MendixInitializer(this, reactHost, reactNativeHost, mendixApplication.useDeveloperSupport) - mendixInitializer.onCreate(mendixApp!!, this, intent.getBooleanExtra(CLEAR_DATA, false)) + MendixInitializer(this, reactHost, mendixApplication.useDeveloperSupport) + mendixInitializer.onCreate(mendixApp!!, intent.getBooleanExtra(CLEAR_DATA, false)) super.onCreate(null) } @@ -42,12 +41,6 @@ open class MendixReactActivity : ReactActivity(), DevAppMenuHandler, LaunchScree super.onDestroy() } - override fun dispatchTouchEvent(ev: MotionEvent): Boolean { - return if (mendixInitializer.dispatchTouchEvent(ev)) { - true - } else super.dispatchTouchEvent(ev) - } - override fun getMainComponentName(): String? { return MAIN_COMPONENT_NAME } @@ -66,7 +59,9 @@ open class MendixReactActivity : ReactActivity(), DevAppMenuHandler, LaunchScree return object : ReactActivityDelegate(this, mainComponentName) { override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean { if (keyCode == KeyEvent.KEYCODE_MENU) { - showDevAppMenu() + if (mendixApp?.showExtendedDevMenu == true) { + showDevAppMenu() + } return true } return super.onKeyUp(keyCode, event) diff --git a/android/src/main/java/com/mendix/mendixnative/devsupport/MendixBridgelessDevSupportManager.kt b/android/src/main/java/com/mendix/mendixnative/devsupport/MendixBridgelessDevSupportManager.kt new file mode 100644 index 0000000..ac9d74c --- /dev/null +++ b/android/src/main/java/com/mendix/mendixnative/devsupport/MendixBridgelessDevSupportManager.kt @@ -0,0 +1,53 @@ +package com.mendix.mendixnative.devsupport + +import android.content.Context +import com.facebook.react.bridge.UiThreadUtil +import com.facebook.react.common.SurfaceDelegateFactory +import com.facebook.react.devsupport.DevSupportManagerBase +import com.facebook.react.devsupport.ReactInstanceDevHelper +import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener +import com.facebook.react.devsupport.interfaces.DevLoadingViewManager +import com.facebook.react.devsupport.interfaces.PausedInDebuggerOverlayManager +import com.facebook.react.devsupport.interfaces.RedBoxHandler +import com.facebook.react.packagerconnection.RequestHandler + +/** + * A public DevSupportManager implementation for Mendix apps in bridgeless mode. + * + * This mirrors the behavior of React Native's internal [BridgelessDevSupportManager] + * but is accessible from external modules, allowing us to inject a custom + * [DevBundleDownloadListener] at construction time without reflection. + */ +class MendixBridgelessDevSupportManager( + applicationContext: Context, + reactInstanceManagerHelper: ReactInstanceDevHelper, + packagerPathForJSBundleName: String?, + enableOnCreate: Boolean, + redBoxHandler: RedBoxHandler?, + devBundleDownloadListener: DevBundleDownloadListener?, + minNumShakes: Int, + customPackagerCommandHandlers: Map?, + surfaceDelegateFactory: SurfaceDelegateFactory?, + devLoadingViewManager: DevLoadingViewManager?, + pausedInDebuggerOverlayManager: PausedInDebuggerOverlayManager?, +) : DevSupportManagerBase( + applicationContext, + reactInstanceManagerHelper, + packagerPathForJSBundleName, + enableOnCreate, + redBoxHandler, + devBundleDownloadListener, + minNumShakes, + customPackagerCommandHandlers, + surfaceDelegateFactory, + devLoadingViewManager, + pausedInDebuggerOverlayManager, +) { + override val uniqueTag: String get() = "MendixBridgeless" + + override fun handleReloadJS() { + UiThreadUtil.assertOnUiThread() + hideRedboxDialog() + reactInstanceDevHelper.reload("MendixBridgelessDevSupportManager.handleReloadJS()") + } +} diff --git a/android/src/main/java/com/mendix/mendixnative/devsupport/MendixDevSupportManagerFactory.kt b/android/src/main/java/com/mendix/mendixnative/devsupport/MendixDevSupportManagerFactory.kt new file mode 100644 index 0000000..67733e1 --- /dev/null +++ b/android/src/main/java/com/mendix/mendixnative/devsupport/MendixDevSupportManagerFactory.kt @@ -0,0 +1,90 @@ +package com.mendix.mendixnative.devsupport + +import android.content.Context +import com.facebook.react.common.SurfaceDelegateFactory +import com.facebook.react.devsupport.DevSupportManagerFactory +import com.facebook.react.devsupport.ReactInstanceDevHelper +import com.facebook.react.devsupport.ReleaseDevSupportManager +import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener +import com.facebook.react.devsupport.interfaces.DevLoadingViewManager +import com.facebook.react.devsupport.interfaces.DevSupportManager +import com.facebook.react.devsupport.interfaces.PausedInDebuggerOverlayManager +import com.facebook.react.devsupport.interfaces.RedBoxHandler +import com.facebook.react.packagerconnection.RequestHandler + +/** + * A [DevSupportManagerFactory] that creates [MendixBridgelessDevSupportManager] instances + * with a custom [DevBundleDownloadListener] injected at construction time. + * + * This replaces the need for reflection-based listener injection. + */ +class MendixDevSupportManagerFactory( + private val devBundleDownloadListener: DevBundleDownloadListener?, +) : DevSupportManagerFactory { + + @Deprecated("Use the create() overload with useDevSupport parameter for New Architecture.") + override fun create( + applicationContext: Context, + reactInstanceManagerHelper: ReactInstanceDevHelper, + packagerPathForJSBundleName: String?, + enableOnCreate: Boolean, + redBoxHandler: RedBoxHandler?, + devBundleDownloadListener: DevBundleDownloadListener?, + minNumShakes: Int, + customPackagerCommandHandlers: Map?, + surfaceDelegateFactory: SurfaceDelegateFactory?, + devLoadingViewManager: DevLoadingViewManager?, + pausedInDebuggerOverlayManager: PausedInDebuggerOverlayManager?, + ): DevSupportManager { + return if (!enableOnCreate) { + ReleaseDevSupportManager() + } else { + MendixBridgelessDevSupportManager( + applicationContext, + reactInstanceManagerHelper, + packagerPathForJSBundleName, + enableOnCreate, + redBoxHandler, + this.devBundleDownloadListener, + minNumShakes, + customPackagerCommandHandlers, + surfaceDelegateFactory, + devLoadingViewManager, + pausedInDebuggerOverlayManager, + ) + } + } + + override fun create( + applicationContext: Context, + reactInstanceManagerHelper: ReactInstanceDevHelper, + packagerPathForJSBundleName: String?, + enableOnCreate: Boolean, + redBoxHandler: RedBoxHandler?, + devBundleDownloadListener: DevBundleDownloadListener?, + minNumShakes: Int, + customPackagerCommandHandlers: Map?, + surfaceDelegateFactory: SurfaceDelegateFactory?, + devLoadingViewManager: DevLoadingViewManager?, + pausedInDebuggerOverlayManager: PausedInDebuggerOverlayManager?, + useDevSupport: Boolean, + ): DevSupportManager { + return if (useDevSupport) { + MendixBridgelessDevSupportManager( + applicationContext, + reactInstanceManagerHelper, + packagerPathForJSBundleName, + enableOnCreate, + redBoxHandler, + this.devBundleDownloadListener, // Our injected listener, ignoring the null passed by ReactHostImpl + minNumShakes, + customPackagerCommandHandlers, + surfaceDelegateFactory, + devLoadingViewManager, + pausedInDebuggerOverlayManager, + ) + } else { + ReleaseDevSupportManager() + } + } +} diff --git a/android/src/main/java/com/mendix/mendixnative/fragment/MendixReactFragment.kt b/android/src/main/java/com/mendix/mendixnative/fragment/MendixReactFragment.kt index 2d44156..7a5c63e 100644 --- a/android/src/main/java/com/mendix/mendixnative/fragment/MendixReactFragment.kt +++ b/android/src/main/java/com/mendix/mendixnative/fragment/MendixReactFragment.kt @@ -3,7 +3,6 @@ package com.mendix.mendixnative.fragment import android.content.Intent import android.os.Bundle import android.view.KeyEvent -import android.view.MotionEvent import com.facebook.react.devsupport.interfaces.DevSupportManager import com.mendix.mendixnative.DevAppMenuHandler import com.mendix.mendixnative.MendixInitializer @@ -67,8 +66,8 @@ open class MendixReactFragment : ReactFragment(), MendixReactFragmentView { val hasRNDeveloperSupport = requireArguments().getBoolean(ARG_USE_DEVELOPER_SUPPORT, false) mendixInitializer = - MendixInitializer(requireActivity(), reactHost!!, reactNativeHost, hasRNDeveloperSupport).also { - it.onCreate(mendixApp!!, this, clearData) + MendixInitializer(requireActivity(), reactHost!!, hasRNDeveloperSupport).also { + it.onCreate(mendixApp!!, clearData) } super.onCreate(savedInstanceState) @@ -91,7 +90,9 @@ open class MendixReactFragment : ReactFragment(), MendixReactFragmentView { view ) ) { - showDevAppMenu() + if (mendixApp?.showExtendedDevMenu == true) { + showDevAppMenu() + } return true } return super.onKeyUp(keyCode, event) @@ -104,23 +105,13 @@ open class MendixReactFragment : ReactFragment(), MendixReactFragmentView { } open fun onCloseProjectSelected() { - // Closing shake detection to avoid dialog from triggering while closing - mendixInitializer.stopShakeDetector(); - } - - override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { - return mendixInitializer.dispatchTouchEvent(ev) } } -interface MendixReactFragmentView : DevAppMenuHandler, TouchEventDispatcher, BackButtonHandler { +interface MendixReactFragmentView : DevAppMenuHandler, BackButtonHandler { fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean } -interface TouchEventDispatcher { - fun dispatchTouchEvent(ev: MotionEvent?): Boolean -} - interface BackButtonHandler { fun onBackPressed(): Boolean } diff --git a/android/src/main/java/com/mendix/mendixnative/react/ClearData.kt b/android/src/main/java/com/mendix/mendixnative/react/ClearData.kt index 9670f69..bf11f00 100644 --- a/android/src/main/java/com/mendix/mendixnative/react/ClearData.kt +++ b/android/src/main/java/com/mendix/mendixnative/react/ClearData.kt @@ -5,7 +5,7 @@ import android.content.Context import android.util.Log import android.webkit.CookieManager import android.widget.Toast -import com.facebook.react.ReactNativeHost +import com.facebook.react.ReactHost import com.facebook.react.bridge.ReactContext import com.facebook.react.modules.network.NetworkingModule import com.mendix.mendixnative.encryption.MendixEncryptedStorage @@ -34,9 +34,9 @@ fun clearData(applicationContext: Application) = clearCookies().also { fileBackend.deleteDirectory(applicationContext.filesDir) } -fun clearDataWithReactContext(applicationContext: Application, reactNativeHost: ReactNativeHost, cb: (success: Boolean) -> Unit) { +fun clearDataWithReactContext(applicationContext: Application, reactHost: ReactHost, cb: (success: Boolean) -> Unit) { clearCachedReactNativeDevBundle(applicationContext) - val reactContext = reactNativeHost.reactContext() + val reactContext = reactHost.currentReactContext val fileBackend = FileBackend(applicationContext) fileBackend.deleteDirectory(applicationContext.filesDir) val errorString = "Clearing %s failed. Please clear your data from the launch screen." @@ -57,7 +57,7 @@ fun clearDataWithReactContext(applicationContext: Application, reactNativeHost: reportError("database") } - if (!clearAsyncStorage(reactNativeHost)) { + if (!clearAsyncStorage(reactHost)) { reportError("async storage") } @@ -88,30 +88,56 @@ fun clearDataWithReactContext(applicationContext: Application, reactNativeHost: } fun deleteAppDatabaseAsync(reactContext: ReactContext?, cb: BooleanCallback) { - reactContext?.typeSafeNativeModule()?.let { - it.deleteAllDBs() - cb(true) - } ?: cb(false) + val opSQLiteModule = reactContext?.nativeModule(OPSQLiteModule.NAME) + if (opSQLiteModule != null) { + try { + opSQLiteModule.deleteAllDBs() + cb(true) + } catch (e: Exception) { + Log.e("ClearData", "Failed to delete databases: ${e.message}") + cb(false) + } + } else { + cb(false) + } } -fun clearAsyncStorage(reactNativeHost: ReactNativeHost): Boolean { - val module = reactNativeHost.typeSafeNativeModule() - if (module != null) { - return true - } else { - return false +fun clearAsyncStorage(reactHost: ReactHost): Boolean { + val asyncStorageModule = reactHost.nativeModule(AsyncStorageModule.NAME) + if (asyncStorageModule != null) { + try { + asyncStorageModule.clear { error -> + if (error != null) { + Log.e("ClearData", "AsyncStorage clear error: $error") + } + } + return true + } catch (e: Exception) { + Log.e("ClearData", "Failed to clear AsyncStorage: ${e.message}") + return false + } } + return false } fun clearSecureStorage(context: Context?): Boolean = context?.let { MendixEncryptedStorage.getMendixEncryptedStorage(it).clear() } ?: false -fun clearCookiesAsync(reactContext: ReactContext?, cb: (success: Boolean) -> Unit) = - reactContext?.typeSafeNativeModule()?.let { module -> - module.clearCookies { - cb(it[0] as Boolean) +fun clearCookiesAsync(reactContext: ReactContext?, cb: (success: Boolean) -> Unit) { + val networkingModule = reactContext?.nativeModule(NetworkingModule.NAME) + if (networkingModule != null) { + try { + networkingModule.clearCookies { result -> + cb(result[0] as Boolean) + } + } catch (e: Exception) { + Log.e("ClearData", "Failed to clear cookies: ${e.message}") + cb(false) } - } ?: cb(false) + } else { + cb(false) + } +} fun clearCachedReactNativeDevBundle(applicationContext: Application) { try { diff --git a/android/src/main/java/com/mendix/mendixnative/react/CloseApp.kt b/android/src/main/java/com/mendix/mendixnative/react/CloseApp.kt index 775d252..ed67964 100644 --- a/android/src/main/java/com/mendix/mendixnative/react/CloseApp.kt +++ b/android/src/main/java/com/mendix/mendixnative/react/CloseApp.kt @@ -1,8 +1,16 @@ package com.mendix.mendixnative.react +import android.util.Log import com.facebook.react.bridge.ReactContext import com.op.sqlite.OPSQLiteModule fun closeSqlDatabaseConnection(reactContext: ReactContext?) { - reactContext?.typeSafeNativeModule()?.closeAllConnections() + val opSQLiteModule = reactContext?.nativeModule(OPSQLiteModule.NAME) + if (opSQLiteModule != null) { + try { + opSQLiteModule.closeAllConnections() + } catch (e: Exception) { + Log.e("CloseApp", "Failed to close database connections: ${e.message}") + } + } } diff --git a/android/src/main/java/com/mendix/mendixnative/react/ModuleHelper.kt b/android/src/main/java/com/mendix/mendixnative/react/ModuleHelper.kt index b39c0f5..7068635 100644 --- a/android/src/main/java/com/mendix/mendixnative/react/ModuleHelper.kt +++ b/android/src/main/java/com/mendix/mendixnative/react/ModuleHelper.kt @@ -1,34 +1,24 @@ package com.mendix.mendixnative.react -import android.annotation.SuppressLint import android.util.Log import com.facebook.react.ReactApplication -import com.facebook.react.ReactNativeHost +import com.facebook.react.ReactHost import com.facebook.react.bridge.NativeModule import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactContext - -inline fun ReactContext.typeSafeNativeModule(): T? { +inline fun ReactContext.nativeModule(name: String): T? { return try { - getNativeModule(T::class.java) + getNativeModule(name) as? T } catch (e: Exception) { Log.e("ModuleAccess", "Error getting module ${T::class.simpleName}", e) null } } -inline fun ReactNativeHost.typeSafeNativeModule(): T? { - return reactContext()?.typeSafeNativeModule() -} - -fun ReactNativeHost.reactContext(): ReactContext? { - return reactInstanceManager.currentReactContext +inline fun ReactHost.nativeModule(name: String): T? { + return currentReactContext?.nativeModule(name) } -fun ReactNativeHost.reactApplicationContext(): ReactApplicationContext? { - val context = reactContext() - if (context is ReactApplicationContext) { - return context - } - return null +inline fun ReactApplicationContext.nativeModule(name: String): T? { + return (applicationContext as? ReactApplication)?.reactHost?.nativeModule(name) } diff --git a/android/src/main/java/com/mendix/mendixnative/react/NativeErrorHandler.kt b/android/src/main/java/com/mendix/mendixnative/react/NativeErrorHandler.kt index 3eca055..9b7487b 100644 --- a/android/src/main/java/com/mendix/mendixnative/react/NativeErrorHandler.kt +++ b/android/src/main/java/com/mendix/mendixnative/react/NativeErrorHandler.kt @@ -3,16 +3,11 @@ package com.mendix.mendixnative.react import com.facebook.common.logging.FLog import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReadableArray -import com.facebook.react.bridge.WritableMap -import com.facebook.react.bridge.WritableNativeMap import com.facebook.react.modules.core.ExceptionsManagerModule class NativeErrorHandler(val reactContext: ReactApplicationContext) { fun handle(message: String?, stackTrace: ReadableArray?) { - reactContext.typeSafeNativeModule()?.reportSoftException(message, stackTrace, 0.0) - // updateExceptionMessage is not available in RN 0.77.1 - // ref: https://github.com/facebook/react-native/commit/4f47439a02183205ff6f68b1fc3bc392e78e4cb4 - // exceptionsManagerModule.updateExceptionMessage(message, stackTrace, 0); + reactContext.nativeModule(ExceptionsManagerModule.NAME)?.reportSoftException(message, stackTrace, 0.0) FLog.e(javaClass, "Received JS exception: $message") } } diff --git a/android/src/main/java/com/mendix/mendixnative/react/ToggleElementInspector.kt b/android/src/main/java/com/mendix/mendixnative/react/ToggleElementInspector.kt deleted file mode 100644 index a22381e..0000000 --- a/android/src/main/java/com/mendix/mendixnative/react/ToggleElementInspector.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.mendix.mendixnative.react - -import com.facebook.react.ReactInstanceManager -import com.facebook.react.bridge.ReactContext -import com.facebook.react.modules.core.DeviceEventManagerModule - -@CopiedFrom(ReactInstanceManager::class) -fun toggleElementInspector(context: ReactContext?) { - context?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) - ?.emit("toggleElementInspector", null) -} diff --git a/android/src/main/java/com/mendix/mendixnative/react/download/DownloadHelper.kt b/android/src/main/java/com/mendix/mendixnative/react/download/DownloadHelper.kt index 55c052b..9a64aff 100644 --- a/android/src/main/java/com/mendix/mendixnative/react/download/DownloadHelper.kt +++ b/android/src/main/java/com/mendix/mendixnative/react/download/DownloadHelper.kt @@ -43,23 +43,31 @@ fun downloadFile( outputFile.parentFile?.mkdirs() outputFile.createNewFile() - client.newCall(Request.Builder().url(url).get().build()).enqueue(object : Callback { - override fun onFailure(call: Call, e: IOException) = onFailure(e) - - override fun onResponse(call: Call, response: Response) { - try { - DownloadResponseHandler( - response, - expectedMimeType, - outputFile, - progressCallback, - ).handle() - onSuccess() - } catch (e: Exception) { + try { + client.newCall(Request.Builder().url(url).get().build()).enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + outputFile.delete() onFailure(e) } - } - }) + + override fun onResponse(call: Call, response: Response) { + try { + DownloadResponseHandler( + response, + expectedMimeType, + outputFile, + progressCallback, + ).handle() + onSuccess() + } catch (e: Exception) { + onFailure(e) + } + } + }) + } catch (e: Exception) { + outputFile.delete() + throw e + } } diff --git a/android/src/main/java/com/mendix/mendixnative/react/fs/NativeFsModule.kt b/android/src/main/java/com/mendix/mendixnative/react/fs/NativeFsModule.kt index 84300cc..15add2a 100644 --- a/android/src/main/java/com/mendix/mendixnative/react/fs/NativeFsModule.kt +++ b/android/src/main/java/com/mendix/mendixnative/react/fs/NativeFsModule.kt @@ -13,6 +13,7 @@ import com.fasterxml.jackson.core.JsonParseException import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.JsonMappingException import com.fasterxml.jackson.databind.ObjectMapper +import com.mendix.mendixnative.react.nativeModule import java.io.File import java.io.FileNotFoundException import java.io.IOException @@ -29,8 +30,8 @@ class NativeFsModule(private val reactContext: ReactApplicationContext) { } fun save(blob: ReadableMap, filePath: String, promise: Promise) { - val blobModule = reactContext.getNativeModule(BlobModule::class.java) - val blobId: String = blob.getString("blobId") ?: run {"" + val blobModule = reactContext.nativeModule(BlobModule.NAME) + val blobId: String = blob.getString("blobId") ?: run { promise.reject(ERROR_INVALID_BLOB, "The specified blob is invalid") return } @@ -149,7 +150,7 @@ class NativeFsModule(private val reactContext: ReactApplicationContext) { fun readAsDataURL(filePath: String, promise: Promise) { try { val fileReaderModule = - reactContext.getNativeModule(FileReaderModule::class.java) + reactContext.nativeModule(FileReaderModule.NAME) fileReaderModule!!.readAsDataURL(read(ensureWhiteListedPath(filePath)), promise) } catch (_: FileNotFoundException) { promise.resolve(null) @@ -241,7 +242,7 @@ class NativeFsModule(private val reactContext: ReactApplicationContext) { private fun read(filePath: String): ReadableMap { val data = fileBackend.read(filePath) - val blobModule = reactContext.getNativeModule(BlobModule::class.java) + val blobModule = reactContext.nativeModule(BlobModule.NAME) val blob: WritableMap = WritableNativeMap() blob.putString("blobId", blobModule!!.store(data)) blob.putInt("offset", 0) diff --git a/android/src/main/java/com/mendix/mendixnative/react/menu/DevAppMenu.kt b/android/src/main/java/com/mendix/mendixnative/react/menu/DevAppMenu.kt deleted file mode 100644 index 51396fb..0000000 --- a/android/src/main/java/com/mendix/mendixnative/react/menu/DevAppMenu.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.mendix.mendixnative.react.menu - -import android.app.Activity -import android.app.AlertDialog -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.widget.Toast -import com.mendix.mendixnative.MendixApplication -import com.mendixnative.R -import com.mendixnative.databinding.AppMenuLayoutBinding -import com.mendix.mendixnative.config.AppPreferences -import com.mendix.mendixnative.react.clearDataWithReactContext -import com.mendix.mendixnative.react.reactContext -import com.mendix.mendixnative.react.toggleElementInspector - -class DevAppMenu( - val activity: Activity, - isDevModeEnabled: Boolean = false, - handleReload: () -> Unit, - onCloseProjectSelected: (() -> Unit)? = null -) : AppMenu { - private val dialog: AlertDialog - - init { - val preferences = AppPreferences(activity.applicationContext) - val binding = AppMenuLayoutBinding.inflate(LayoutInflater.from(activity)) - val view = binding.root - - binding.advancedSettingsButton - - dialog = AlertDialog.Builder(activity) - .setView(view) - .create() - - binding.advancedSettingsContainer.visibility = View.GONE - binding.advancedSettingsButton.visibility = visibleWhenDevModeEnabled(isDevModeEnabled) - binding.advancedSettingsButton.setOnClickListener { - binding.advancedSettingsContainer.visibility = - when (binding.advancedSettingsContainer.visibility) { - (View.GONE) -> View.VISIBLE - else -> View.GONE - } - } - - binding.remoteDebuggingButton.visibility = visibleWhenDevModeEnabled(isDevModeEnabled) - binding.remoteDebuggingButton.text = - activity.resources.getText(remoteDebugginButtonTextResource(preferences.isRemoteJSDebugEnabled)) - binding.remoteDebuggingButton.setOnClickListener { - preferences.setRemoteDebugging(!preferences.isRemoteJSDebugEnabled) - handleReload() - binding.remoteDebuggingButton.text = - activity.resources.getText(remoteDebugginButtonTextResource(preferences.isRemoteJSDebugEnabled)) - dialog.dismiss() - } - - binding.advancedClearData.setOnClickListener { - activity.runOnUiThread { - clearDataWithReactContext( - activity.application, - (activity.application as MendixApplication).reactNativeHost - ) { success: Boolean -> - if (success) { - activity.runOnUiThread { - handleReload() - } - } else { - Toast.makeText(activity, "Clearing data failed.", Toast.LENGTH_LONG).show() - } - } - } - dialog.dismiss() - } - - binding.elementInspectorButton.visibility = visibleWhenDevModeEnabled(isDevModeEnabled) - binding.elementInspectorButton.setOnClickListener { - preferences.setElementInspector(!preferences.isElementInspectorEnabled) - toggleElementInspector((activity.application as MendixApplication).reactNativeHost.reactContext()) - dialog.dismiss() - } - - binding.reloadButton.setOnClickListener { - handleReload() - dialog.dismiss() - } - - binding.closeButton.setOnClickListener { - dialog.dismiss() - onCloseProjectSelected?.invoke() - } - } - - override fun show() { - if (!activity.isDestroyed) { - dialog.show() - } else { - Log.d("DevAppMenu", "Attempted to show dialog in a destroyed activity") - } - } - - private fun visibleWhenDevModeEnabled(devModeEnabled: Boolean): Int = if (devModeEnabled) { - View.VISIBLE - } else { - View.GONE - } - - private fun remoteDebugginButtonTextResource(isRemoteJsDebugEnabled: Boolean): Int = - if (isRemoteJsDebugEnabled) { - R.string.dev_menu_disable_remote_debugging - } else { - R.string.dev_menu_enable_remote_debugging - } -} diff --git a/android/src/main/java/com/mendixnative/MendixNativeModule.kt b/android/src/main/java/com/mendixnative/MendixNativeModule.kt deleted file mode 100644 index c050cd5..0000000 --- a/android/src/main/java/com/mendixnative/MendixNativeModule.kt +++ /dev/null @@ -1,183 +0,0 @@ -package com.mendixnative - -import com.facebook.react.bridge.Promise -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.bridge.ReadableArray -import com.facebook.react.bridge.ReadableMap -import com.facebook.react.bridge.WritableMap -import com.facebook.react.module.annotations.ReactModule -import com.mendix.mendixnative.encryption.MendixEncryptedStorageModule -import com.mendix.mendixnative.react.MxConfiguration -import com.mendix.mendixnative.react.NativeErrorHandler -import com.mendix.mendixnative.react.NativeReloadHandler -import com.mendix.mendixnative.react.NavigationModeModule -import com.mendix.mendixnative.react.cookie.NativeCookieModule -import com.mendix.mendixnative.react.download.NativeDownloadModule -import com.mendix.mendixnative.react.fs.NativeFsModule -import com.mendix.mendixnative.react.ota.NativeOtaModule -import com.mendix.mendixnative.react.splash.MendixSplashScreenModule -import com.mendix.mendixnative.react.splash.MendixSplashScreenPresenter - -@ReactModule(name = MendixNativeModule.NAME) -class MendixNativeModule(reactContext: ReactApplicationContext) : NativeMendixNativeSpec(reactContext) { - - var presenter: MendixSplashScreenPresenter? = null - - override fun getName(): String { - return NAME - } - - override fun encryptedStorageSetItem(key: String, value: String, promise: Promise) { - MendixEncryptedStorageModule(reactApplicationContext).setItem(key, value, promise) - } - - override fun encryptedStorageGetItem(key: String, promise: Promise) { - MendixEncryptedStorageModule(reactApplicationContext).getItem(key, promise) - } - - override fun encryptedStorageRemoveItem(key: String, promise: Promise) { - MendixEncryptedStorageModule(reactApplicationContext).removeItem(key, promise) - } - - override fun encryptedStorageClear(promise: Promise) { - MendixEncryptedStorageModule(reactApplicationContext).clear(promise) - } - - override fun encryptedStorageIsEncrypted(): Boolean { - return MendixEncryptedStorageModule(reactApplicationContext).isEncrypted - } - - override fun splashScreenShow() { - MendixSplashScreenModule(reactApplicationContext).show(presenter) - } - - override fun splashScreenHide() { - MendixSplashScreenModule(reactApplicationContext).hide(presenter) - } - - override fun cookieClearAll(promise: Promise) { - NativeCookieModule(reactApplicationContext).clearAll(promise) - } - - override fun reloadHandlerReload(promise: Promise) { - NativeReloadHandler(reactApplicationContext).reload() - promise.resolve(null) - } - - fun reloadClientWithState() { - emitOnReloadWithState() - } - - override fun reloadHandlerExitApp(promise: Promise) { - NativeReloadHandler(reactApplicationContext).exitApp() - promise.resolve(null) - } - - override fun downloadHandlerDownload( - url: String, - downloadPath: String, - config: ReadableMap, - promise: Promise - ) { - NativeDownloadModule(reactApplicationContext).download( - url, downloadPath, config, promise - ) - } - - override fun mxConfigurationGetConfig(): WritableMap? { - return MxConfiguration(reactApplicationContext).getConstants() - } - - override fun otaDownload( - config: ReadableMap, - promise: Promise - ) { - NativeOtaModule(reactApplicationContext).download(config, promise) - } - - override fun otaDeploy( - config: ReadableMap, - promise: Promise - ) { - NativeOtaModule(reactApplicationContext).deploy(config, promise) - } - - override fun fsSetEncryptionEnabled(enabled: Boolean) { - NativeFsModule(reactApplicationContext).setEncryptionEnabled(enabled) - } - - override fun fsSave( - blob: ReadableMap, - filePath: String, - promise: Promise - ) { - NativeFsModule(reactApplicationContext).save(blob, filePath, promise) - } - - override fun fsRead(filePath: String, promise: Promise) { - NativeFsModule(reactApplicationContext).read(filePath, promise) - } - - override fun fsMove( - filePath: String, - newPath: String, - promise: Promise - ) { - NativeFsModule(reactApplicationContext).move(filePath, newPath, promise) - } - - override fun fsRemove(filePath: String, promise: Promise) { - NativeFsModule(reactApplicationContext).remove(filePath, promise) - } - - override fun fsList(dirPath: String, promise: Promise) { - NativeFsModule(reactApplicationContext).list(dirPath, promise) - } - - override fun fsReadAsDataURL( - filePath: String, - promise: Promise - ) { - NativeFsModule(reactApplicationContext).readAsDataURL(filePath, promise) - } - - override fun fsReadAsText(filePath: String, promise: Promise) { - NativeFsModule(reactApplicationContext).readAsText(filePath, promise) - } - - override fun fsFileExists(filePath: String, promise: Promise) { - NativeFsModule(reactApplicationContext).fileExists(filePath, promise) - } - - override fun fsWriteJson( - data: ReadableMap, - filepath: String, - promise: Promise - ) { - NativeFsModule(reactApplicationContext).writeJson(data, filepath, promise) - } - - override fun fsReadJson(filepath: String, promise: Promise) { - NativeFsModule(reactApplicationContext).readJson(filepath, promise) - } - - override fun fsConstants(): WritableMap? { - return NativeFsModule(reactApplicationContext).getConstants() - } - - override fun errorHandlerHandle(message: String, stackTrace: ReadableArray) { - NativeErrorHandler(reactApplicationContext).handle(message, stackTrace) - } - - override fun navigationModeIsNavigationBarActive(): Boolean { - return NavigationModeModule(reactApplicationContext).isNavigationBarActive() - } - - override fun navigationModeGetNavigationBarHeight(): Double { - return NavigationModeModule(reactApplicationContext).getNavigationBarHeight() - } - - companion object { - const val NAME = "MendixNative" - } -} diff --git a/android/src/main/java/com/mendixnative/MendixNativePackage.kt b/android/src/main/java/com/mendixnative/MendixNativePackage.kt index 882a1d6..9560089 100644 --- a/android/src/main/java/com/mendixnative/MendixNativePackage.kt +++ b/android/src/main/java/com/mendixnative/MendixNativePackage.kt @@ -6,6 +6,16 @@ import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.module.model.ReactModuleInfo import com.facebook.react.module.model.ReactModuleInfoProvider import com.mendix.mendixnative.react.splash.MendixSplashScreenPresenter +import com.mendixnative.configuration.MxConfigurationModule +import com.mendixnative.cookie.MxCookieModule +import com.mendixnative.download.MxDownloadModule +import com.mendixnative.encryption.MxEncryptionModule +import com.mendixnative.error.MxErrorModule +import com.mendixnative.fs.MxFileSystemModule +import com.mendixnative.navigation.MxNavigationModule +import com.mendixnative.ota.MxOtaModule +import com.mendixnative.reload.MxReloadModule +import com.mendixnative.splash.MxSplashScreenModule import java.util.HashMap class MendixNativePackage : BaseReactPackage() { @@ -13,26 +23,49 @@ class MendixNativePackage : BaseReactPackage() { var splashScreenPresenter: MendixSplashScreenPresenter? = null override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? { - return if (name == MendixNativeModule.NAME) { - val module = MendixNativeModule(reactContext) - module.presenter = splashScreenPresenter - return module - } else { - null + return when (name) { + MxEncryptionModule.NAME -> MxEncryptionModule(reactContext) + MxSplashScreenModule.NAME -> { + val module = MxSplashScreenModule(reactContext) + module.presenter = splashScreenPresenter + module + } + MxFileSystemModule.NAME -> MxFileSystemModule(reactContext) + MxOtaModule.NAME -> MxOtaModule(reactContext) + MxDownloadModule.NAME -> MxDownloadModule(reactContext) + MxReloadModule.NAME -> MxReloadModule(reactContext) + MxConfigurationModule.NAME -> MxConfigurationModule(reactContext) + MxCookieModule.NAME -> MxCookieModule(reactContext) + MxErrorModule.NAME -> MxErrorModule(reactContext) + MxNavigationModule.NAME -> MxNavigationModule(reactContext) + else -> null } } override fun getReactModuleInfoProvider(): ReactModuleInfoProvider { return ReactModuleInfoProvider { val moduleInfos: MutableMap = HashMap() - moduleInfos[MendixNativeModule.NAME] = ReactModuleInfo( - MendixNativeModule.NAME, - MendixNativeModule.NAME, - false, // canOverrideExistingModule - false, // needsEagerInit - false, // isCxxModule - true // isTurboModule - ) + listOf( + MxEncryptionModule.NAME, + MxSplashScreenModule.NAME, + MxFileSystemModule.NAME, + MxOtaModule.NAME, + MxDownloadModule.NAME, + MxReloadModule.NAME, + MxConfigurationModule.NAME, + MxCookieModule.NAME, + MxErrorModule.NAME, + MxNavigationModule.NAME + ).forEach { + moduleInfos[it] = ReactModuleInfo( + name = it, + className = it, + canOverrideExistingModule = false, + needsEagerInit = false, + isCxxModule = false, + isTurboModule = true + ) + } moduleInfos } } diff --git a/android/src/main/java/com/mendixnative/configuration/MxConfigurationModule.kt b/android/src/main/java/com/mendixnative/configuration/MxConfigurationModule.kt new file mode 100644 index 0000000..8af3df0 --- /dev/null +++ b/android/src/main/java/com/mendixnative/configuration/MxConfigurationModule.kt @@ -0,0 +1,24 @@ +package com.mendixnative.configuration + +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.WritableMap +import com.facebook.react.module.annotations.ReactModule +import com.mendix.mendixnative.react.MxConfiguration +import com.mendixnative.NativeMxConfigurationSpec + +@ReactModule(name = MxConfigurationModule.NAME) +class MxConfigurationModule(reactContext: ReactApplicationContext) : + NativeMxConfigurationSpec(reactContext) { + + private val configuration = MxConfiguration(reactContext) + + override fun getName(): String = NAME + + override fun getConfig(): WritableMap? { + return configuration.getConstants() + } + + companion object { + const val NAME = "MxConfiguration" + } +} diff --git a/android/src/main/java/com/mendixnative/cookie/MxCookieModule.kt b/android/src/main/java/com/mendixnative/cookie/MxCookieModule.kt new file mode 100644 index 0000000..edcb843 --- /dev/null +++ b/android/src/main/java/com/mendixnative/cookie/MxCookieModule.kt @@ -0,0 +1,24 @@ +package com.mendixnative.cookie + +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.module.annotations.ReactModule +import com.mendix.mendixnative.react.cookie.NativeCookieModule +import com.mendixnative.NativeMxCookieSpec + +@ReactModule(name = MxCookieModule.NAME) +class MxCookieModule(reactContext: ReactApplicationContext) : + NativeMxCookieSpec(reactContext) { + + private val cookieModule = NativeCookieModule(reactContext) + + override fun getName(): String = NAME + + override fun clearAll(promise: Promise) { + cookieModule.clearAll(promise) + } + + companion object { + const val NAME = "MxCookie" + } +} diff --git a/android/src/main/java/com/mendixnative/download/MxDownloadModule.kt b/android/src/main/java/com/mendixnative/download/MxDownloadModule.kt new file mode 100644 index 0000000..843f0ad --- /dev/null +++ b/android/src/main/java/com/mendixnative/download/MxDownloadModule.kt @@ -0,0 +1,25 @@ +package com.mendixnative.download + +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.module.annotations.ReactModule +import com.mendix.mendixnative.react.download.NativeDownloadModule +import com.mendixnative.NativeMxDownloadSpec + +@ReactModule(name = MxDownloadModule.NAME) +class MxDownloadModule(reactContext: ReactApplicationContext) : + NativeMxDownloadSpec(reactContext) { + + private val downloadModule = NativeDownloadModule(reactContext) + + override fun getName(): String = NAME + + override fun download(url: String, downloadPath: String, config: ReadableMap, promise: Promise) { + downloadModule.download(url, downloadPath, config, promise) + } + + companion object { + const val NAME = "MxDownload" + } +} diff --git a/android/src/main/java/com/mendixnative/encryption/MxEncryptionModule.kt b/android/src/main/java/com/mendixnative/encryption/MxEncryptionModule.kt new file mode 100644 index 0000000..9c4f9e7 --- /dev/null +++ b/android/src/main/java/com/mendixnative/encryption/MxEncryptionModule.kt @@ -0,0 +1,40 @@ +package com.mendixnative.encryption + +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.module.annotations.ReactModule +import com.mendix.mendixnative.encryption.MendixEncryptedStorageModule +import com.mendixnative.NativeMxEncryptionSpec + +@ReactModule(name = MxEncryptionModule.NAME) +class MxEncryptionModule(reactContext: ReactApplicationContext) : + NativeMxEncryptionSpec(reactContext) { + + private val encryptedStorage = MendixEncryptedStorageModule(reactContext) + + override fun getName(): String = NAME + + override fun setItem(key: String, value: String, promise: Promise) { + encryptedStorage.setItem(key, value, promise) + } + + override fun getItem(key: String, promise: Promise) { + encryptedStorage.getItem(key, promise) + } + + override fun removeItem(key: String, promise: Promise) { + encryptedStorage.removeItem(key, promise) + } + + override fun clear(promise: Promise) { + encryptedStorage.clear(promise) + } + + override fun isEncrypted(): Boolean { + return encryptedStorage.isEncrypted + } + + companion object { + const val NAME = "MxEncryption" + } +} diff --git a/android/src/main/java/com/mendixnative/error/MxErrorModule.kt b/android/src/main/java/com/mendixnative/error/MxErrorModule.kt new file mode 100644 index 0000000..0e0703c --- /dev/null +++ b/android/src/main/java/com/mendixnative/error/MxErrorModule.kt @@ -0,0 +1,24 @@ +package com.mendixnative.error + +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReadableArray +import com.facebook.react.module.annotations.ReactModule +import com.mendix.mendixnative.react.NativeErrorHandler +import com.mendixnative.NativeMxErrorSpec + +@ReactModule(name = MxErrorModule.NAME) +class MxErrorModule(reactContext: ReactApplicationContext) : + NativeMxErrorSpec(reactContext) { + + private val errorHandler = NativeErrorHandler(reactContext) + + override fun getName(): String = NAME + + override fun handle(message: String, stackTrace: ReadableArray) { + errorHandler.handle(message, stackTrace) + } + + companion object { + const val NAME = "MxError" + } +} diff --git a/android/src/main/java/com/mendixnative/fs/MxFileSystemModule.kt b/android/src/main/java/com/mendixnative/fs/MxFileSystemModule.kt new file mode 100644 index 0000000..2bef53e --- /dev/null +++ b/android/src/main/java/com/mendixnative/fs/MxFileSystemModule.kt @@ -0,0 +1,70 @@ +package com.mendixnative.fs + +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.bridge.WritableMap +import com.facebook.react.module.annotations.ReactModule +import com.mendix.mendixnative.react.fs.NativeFsModule +import com.mendixnative.NativeMxFileSystemSpec + +@ReactModule(name = MxFileSystemModule.NAME) +class MxFileSystemModule(reactContext: ReactApplicationContext) : + NativeMxFileSystemSpec(reactContext) { + + private val fsModule = NativeFsModule(reactContext) + + override fun getName(): String = NAME + + override fun constants(): WritableMap? { + return fsModule.getConstants() + } + + override fun save(blob: ReadableMap, filePath: String, promise: Promise) { + fsModule.save(blob, filePath, promise) + } + + override fun read(filePath: String, promise: Promise) { + fsModule.read(filePath, promise) + } + + override fun move(filePath: String, newPath: String, promise: Promise) { + fsModule.move(filePath, newPath, promise) + } + + override fun remove(filePath: String, promise: Promise) { + fsModule.remove(filePath, promise) + } + + override fun list(dirPath: String, promise: Promise) { + fsModule.list(dirPath, promise) + } + + override fun readAsDataURL(filePath: String, promise: Promise) { + fsModule.readAsDataURL(filePath, promise) + } + + override fun readAsText(filePath: String, promise: Promise) { + fsModule.readAsText(filePath, promise) + } + + override fun fileExists(filePath: String, promise: Promise) { + fsModule.fileExists(filePath, promise) + } + + override fun writeJson(data: ReadableMap, filepath: String, promise: Promise) { + fsModule.writeJson(data, filepath, promise) + } + + override fun readJson(filepath: String, promise: Promise) { + fsModule.readJson(filepath, promise) + } + + override fun setEncryptionEnabled(enabled: Boolean) { + fsModule.setEncryptionEnabled(enabled) + } + + companion object { + const val NAME = "MxFileSystem" + } +} diff --git a/android/src/main/java/com/mendixnative/navigation/MxNavigationModule.kt b/android/src/main/java/com/mendixnative/navigation/MxNavigationModule.kt new file mode 100644 index 0000000..fb52f19 --- /dev/null +++ b/android/src/main/java/com/mendixnative/navigation/MxNavigationModule.kt @@ -0,0 +1,27 @@ +package com.mendixnative.navigation + +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.module.annotations.ReactModule +import com.mendix.mendixnative.react.NavigationModeModule +import com.mendixnative.NativeMxNavigationSpec + +@ReactModule(name = MxNavigationModule.NAME) +class MxNavigationModule(reactContext: ReactApplicationContext) : + NativeMxNavigationSpec(reactContext) { + + private val navigationMode = NavigationModeModule(reactContext) + + override fun getName(): String = NAME + + override fun isNavigationBarActive(): Boolean { + return navigationMode.isNavigationBarActive() + } + + override fun getNavigationBarHeight(): Double { + return navigationMode.getNavigationBarHeight() + } + + companion object { + const val NAME = "MxNavigation" + } +} diff --git a/android/src/main/java/com/mendixnative/ota/MxOtaModule.kt b/android/src/main/java/com/mendixnative/ota/MxOtaModule.kt new file mode 100644 index 0000000..8ff1258 --- /dev/null +++ b/android/src/main/java/com/mendixnative/ota/MxOtaModule.kt @@ -0,0 +1,29 @@ +package com.mendixnative.ota + +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.module.annotations.ReactModule +import com.mendix.mendixnative.react.ota.NativeOtaModule +import com.mendixnative.NativeMxOtaSpec + +@ReactModule(name = MxOtaModule.NAME) +class MxOtaModule(reactContext: ReactApplicationContext) : + NativeMxOtaSpec(reactContext) { + + private val otaModule = NativeOtaModule(reactContext) + + override fun getName(): String = NAME + + override fun download(config: ReadableMap, promise: Promise) { + otaModule.download(config, promise) + } + + override fun deploy(config: ReadableMap, promise: Promise) { + otaModule.deploy(config, promise) + } + + companion object { + const val NAME = "MxOta" + } +} diff --git a/android/src/main/java/com/mendixnative/reload/MxReloadModule.kt b/android/src/main/java/com/mendixnative/reload/MxReloadModule.kt new file mode 100644 index 0000000..dd6e66d --- /dev/null +++ b/android/src/main/java/com/mendixnative/reload/MxReloadModule.kt @@ -0,0 +1,30 @@ +package com.mendixnative.reload + +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.module.annotations.ReactModule +import com.mendix.mendixnative.react.NativeReloadHandler +import com.mendixnative.NativeMxReloadSpec + +@ReactModule(name = MxReloadModule.NAME) +class MxReloadModule(reactContext: ReactApplicationContext) : + NativeMxReloadSpec(reactContext) { + + private val reloadHandler = NativeReloadHandler(reactContext) + + override fun getName(): String = NAME + + override fun reload(promise: Promise) { + reloadHandler.reload() + promise.resolve(null) + } + + override fun exitApp(promise: Promise) { + reloadHandler.exitApp() + promise.resolve(null) + } + + companion object { + const val NAME = "MxReload" + } +} diff --git a/android/src/main/java/com/mendixnative/splash/MxSplashScreenModule.kt b/android/src/main/java/com/mendixnative/splash/MxSplashScreenModule.kt new file mode 100644 index 0000000..86eb385 --- /dev/null +++ b/android/src/main/java/com/mendixnative/splash/MxSplashScreenModule.kt @@ -0,0 +1,31 @@ +package com.mendixnative.splash + +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.module.annotations.ReactModule +import com.mendix.mendixnative.react.splash.MendixSplashScreenModule +import com.mendix.mendixnative.react.splash.MendixSplashScreenPresenter +import com.mendixnative.NativeMxSplashScreenSpec + +@ReactModule(name = MxSplashScreenModule.NAME) +class MxSplashScreenModule(reactContext: ReactApplicationContext) : + NativeMxSplashScreenSpec(reactContext) { + + private val splashScreenModule = MendixSplashScreenModule(reactContext) + + // Presenter is injected by MendixNativePackage + var presenter: MendixSplashScreenPresenter? = null + + override fun getName(): String = NAME + + override fun show() { + splashScreenModule.show(presenter) + } + + override fun hide() { + splashScreenModule.hide(presenter) + } + + companion object { + const val NAME = "MxSplashScreen" + } +} diff --git a/android/src/main/res/layout/app_menu_layout.xml b/android/src/main/res/layout/app_menu_layout.xml deleted file mode 100644 index ce948e6..0000000 --- a/android/src/main/res/layout/app_menu_layout.xml +++ /dev/null @@ -1,94 +0,0 @@ - - - -