package expo.modules.pngme import expo.modules.kotlin.modules.Module import expo.modules.kotlin.modules.ModuleDefinition import expo.modules.kotlin.Promise import expo.modules.kotlin.records.Field import expo.modules.kotlin.records.Record import android.Manifest import android.content.pm.PackageManager import android.graphics.Color import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import com.pngme.sdk.library.PngmeSdk import com.pngme.sdk.library.views.PngmeDialogStyle import com.pngme.sdk.library.common.Callback class PngmeDialogStyleRecord : Record { @Field var primaryColor: String? = null @Field var backgroundColor: String? = null @Field var textColor: String? = null @Field var buttonBackgroundColor: String? = null @Field var buttonTextColor: String? = null @Field var linkTextColor: String? = null @Field var titleTextSize: Float? = null @Field var bodyTextSize: Float? = null @Field var buttonTextSize: Float? = null @Field var closeIconColor: String? = null @Field var smsIconColor: String? = null @Field var privacyIconColor: String? = null @Field var customTitle: String? = null @Field var customSmsDescription: String? = null @Field var customPrivacyDescription: String? = null @Field var customButtonText: String? = null @Field var buttonCornerRadius: Float? = null @Field var buttonElevation: Float? = null @Field var privacyPolicyUrl: String? = null @Field var eulaUrl: String? = null @Field var contentPadding: Int? = null } class PngmeConfigRecord : Record { @Field var clientKey: String = "" @Field var firstName: String? = null @Field var lastName: String? = null @Field var email: String? = null @Field var phoneNumber: String? = null @Field var externalId: String = "" @Field var companyName: String = "" @Field var dialogStyle: PngmeDialogStyleRecord? = null } class PngmeModule : Module() { private val context get() = requireNotNull(appContext.reactContext) private val currentActivity get() = appContext.currentActivity as? AppCompatActivity override fun definition() = ModuleDefinition { Name("PngmeModule") Events( "onPermissionGranted", "onPermissionDenied", "onDialogDismissed", "onSignInComplete", "onSmsUploadStarted", "onSmsUploadComplete", "onError" ) AsyncFunction("go") { config: PngmeConfigRecord, promise: Promise -> val activity = currentActivity if (activity == null) { promise.reject( "ERR_NO_ACTIVITY", "No current activity available. Make sure the app is in the foreground.", null ) return@AsyncFunction } try { val dialogStyle = config.dialogStyle?.let { convertToNativeStyle(it) } val onComplete = Callback { sendEvent("onDialogDismissed", mapOf("completed" to true)) val isGranted = PngmeSdk.isPermissionGranted(context) val status = getPermissionStatusMap() if (isGranted) { sendEvent("onPermissionGranted", mapOf("status" to status)) PngmeSdk.getUserUuid(context)?.let { uuid -> sendEvent("onSignInComplete", mapOf("userId" to uuid)) } } else { sendEvent("onPermissionDenied", mapOf("status" to status)) } promise.resolve(null) } PngmeSdk.go( activity = activity, clientKey = config.clientKey, firstName = config.firstName ?: "", lastName = config.lastName ?: "", email = config.email ?: "", phoneNumber = config.phoneNumber ?: "", externalId = config.externalId, companyName = config.companyName, onComplete = onComplete, dialogStyle = dialogStyle ) } catch (e: Exception) { sendEvent("onError", mapOf( "error" to (e.message ?: "Unknown error"), "code" to "SDK_ERROR" )) promise.reject("ERR_SDK_INIT", "Failed to initialize SDK: ${e.message}", e) } } AsyncFunction("isPermissionGranted") { promise: Promise -> try { val isGranted = PngmeSdk.isPermissionGranted(context) promise.resolve(isGranted) } catch (e: Exception) { promise.reject("ERR_PERMISSION_CHECK", "Failed to check permission", e) } } AsyncFunction("getUserUuid") { promise: Promise -> try { val uuid = PngmeSdk.getUserUuid(context) promise.resolve(uuid) } catch (e: Exception) { promise.reject("ERR_GET_UUID", "Failed to get user UUID", e) } } AsyncFunction("setDefaultStyle") { style: PngmeDialogStyleRecord, promise: Promise -> try { val nativeStyle = convertToNativeStyle(style) PngmeSdk.setDefaultStyle(nativeStyle) promise.resolve(null) } catch (e: Exception) { promise.reject("ERR_SET_STYLE", "Failed to set default style", e) } } AsyncFunction("clearDefaultStyle") { promise: Promise -> try { PngmeSdk.clearDefaultStyle() promise.resolve(null) } catch (e: Exception) { promise.reject("ERR_CLEAR_STYLE", "Failed to clear default style", e) } } AsyncFunction("getPermissionStatus") { promise: Promise -> try { promise.resolve(getPermissionStatusMap()) } catch (e: Exception) { promise.reject("ERR_GET_STATUS", "Failed to get permission status", e) } } } private fun convertToNativeStyle(style: PngmeDialogStyleRecord): PngmeDialogStyle { return PngmeDialogStyle( primaryColor = style.primaryColor?.let { parseColor(it) }, backgroundColor = style.backgroundColor?.let { parseColor(it) }, textColor = style.textColor?.let { parseColor(it) }, buttonBackgroundColor = style.buttonBackgroundColor?.let { parseColor(it) }, buttonTextColor = style.buttonTextColor?.let { parseColor(it) }, linkTextColor = style.linkTextColor?.let { parseColor(it) }, titleTextSize = style.titleTextSize, bodyTextSize = style.bodyTextSize, buttonTextSize = style.buttonTextSize, closeIconTint = style.closeIconColor?.let { parseColor(it) }, smsIconTint = style.smsIconColor?.let { parseColor(it) }, privacyIconTint = style.privacyIconColor?.let { parseColor(it) }, customTitle = style.customTitle, customSmsDescription = style.customSmsDescription, customPrivacyDescription = style.customPrivacyDescription, customButtonText = style.customButtonText, buttonCornerRadius = style.buttonCornerRadius, buttonElevation = style.buttonElevation, privacyPolicyUrl = style.privacyPolicyUrl, eulaUrl = style.eulaUrl, contentPadding = style.contentPadding ) } private fun parseColor(colorString: String): Int { return try { if (colorString.startsWith("#")) { Color.parseColor(colorString) } else { Color.parseColor("#$colorString") } } catch (e: Exception) { Color.BLACK // Default fallback } } private fun getPermissionStatusMap(): Map { val smsPermission = when { ContextCompat.checkSelfPermission(context, Manifest.permission.READ_SMS) == PackageManager.PERMISSION_GRANTED -> "granted" context.getSharedPreferences("pngme_prefs", 0) .getBoolean("sms_asked", false) -> "denied" else -> "never_asked" } val termsAccepted = try { val prefs = context.getSharedPreferences("pr3f3r3nc3", 0) prefs.getBoolean("hasAcceptedTerms", false) } catch (e: Exception) { false } return mapOf( "smsPermission" to smsPermission, "termsAccepted" to termsAccepted ) } }