package com.linkreactnativesdk import android.util.Log import android.view.Choreographer import android.view.View import android.view.ViewGroup import android.widget.FrameLayout import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.FragmentActivity import androidx.lifecycle.ViewModelProvider import com.facebook.react.common.MapBuilder import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.WritableMap import com.facebook.react.bridge.Arguments import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.ViewGroupManager import com.facebook.react.uimanager.annotations.ReactPropGroup import com.facebook.react.uimanager.annotations.ReactProp import com.facebook.react.uimanager.events.RCTEventEmitter import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import money.link.linkaccount.* import money.link.linkaccount.model.SessionResult class LinkReactNativeSdkViewManager(reactContext: ReactApplicationContext) : ViewGroupManager() { var reactContext: ReactApplicationContext private var dialog: LinkAccountDialog? = null private var sessionKey = "" private var environment = LinkEnvironment.PRODUCTION private var redirectUrl = "" private var propWidth = 0 private var propHeight = 0 private var reactNativeViewId = 0 override fun getName(): String { return REACT_CLASS } init { this.reactContext = reactContext } /** * Return a FrameLayout which will later hold the Fragment */ override fun createViewInstance(reactContext: ThemedReactContext): FrameLayout { return FrameLayout(reactContext) } /** * Map the "create" command to an integer */ override fun getCommandsMap(): Map? { return MapBuilder.of( "create", COMMAND_CREATE ) } /** * Handle "create" command (called from JS) and call createFragment method */ override fun receiveCommand( root: FrameLayout, commandId: String, args: ReadableArray? ) { super.receiveCommand(root, commandId, args) val reactNativeViewId: Int = args!!.getInt(0) val commandIdInt = commandId.toInt() when (commandIdInt) { COMMAND_CREATE -> createFragment(root, reactNativeViewId) else -> { } } } @ReactPropGroup(names = ["width", "height"], customType = "Style") fun setStyle(view: FrameLayout, index: Int, value: Int) { if (index == 0) { propWidth = value } if (index == 1) { propHeight = value } } override fun getExportedCustomDirectEventTypeConstants(): Map> { return MapBuilder.of( "onSuccess", MapBuilder.of("registrationName", "onSuccess"), "onFailure", MapBuilder.of("registrationName", "onFailure"), "onBack", MapBuilder.of("registrationName", "onBack"), ); } private fun onSessionResult(result: SessionResult) { result.onSuccess { result -> val event: WritableMap = Arguments.createMap() event.putString("customerId", result.customerId) event.putString("paymentId", result.paymentId) event.putString("paymentStatus", result.paymentStatus?.name) reactContext .getJSModule(RCTEventEmitter::class.java) .receiveEvent(reactNativeViewId, "onSuccess", event); } result.onFailure { exception -> val event: WritableMap = Arguments.createMap() event.putString("message", exception.message) (exception as LinkError).statusCode?.let { code -> event.putInt("code", code) } reactContext .getJSModule(RCTEventEmitter::class.java) .receiveEvent(reactNativeViewId, "onFailure", event); } } @ReactProp(name = "sessionKey") fun setSessionKey(view: FrameLayout, sessionKey: String) { this.sessionKey = sessionKey } @ReactProp(name = "environment") fun setEnvironment(view: FrameLayout, environment: String) { if (environment == "sandbox") { this.environment = LinkEnvironment.SANDBOX } else if (environment == "development") { this.environment = LinkEnvironment.DEVELOPMENT } } @ReactProp(name = "redirectUrl") fun setRedirectUrl(view: FrameLayout, redirectUrl: String) { this.redirectUrl = redirectUrl } fun createFragment(root: FrameLayout, reactNativeViewId: Int) { Log.i(TAG, "In createFragment") this.reactNativeViewId = reactNativeViewId val parentView = root.findViewById(reactNativeViewId) as ViewGroup setupLayout(parentView) dialog = LinkAccountDialog.create(sessionKey, environment, redirectUrl) val activity = reactContext.getCurrentActivity() as FragmentActivity dialog?.let { dialog -> dialog.display(activity, this::onSessionResult) { val event: WritableMap = Arguments.createMap() event.putString("source", "Browser Close") Log.i(TAG, "onBack is about to run") reactContext .getJSModule(RCTEventEmitter::class.java) .receiveEvent(reactNativeViewId, "onBack", event) Log.i(TAG, "onBack ran") } } } fun setupLayout(view: View) { Choreographer.getInstance().postFrameCallback(object : Choreographer.FrameCallback { override fun doFrame(frameTimeNanos: Long) { manuallyLayoutChildren(view) view.viewTreeObserver.dispatchOnGlobalLayout() Choreographer.getInstance().postFrameCallback(this) } }) } fun manuallyLayoutChildren(view: View) { // propWidth and propHeight coming from react-native props val width = propWidth val height = propHeight view.measure( View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY) ) view.layout(0, 0, width, height) } companion object { const val TAG = "LRNSVM" const val REACT_CLASS = "LinkReactNativeSdkViewManager" const val COMMAND_CREATE = 1 } }