package com.myfatoorahreactnative.cardviewv3 import android.graphics.Color import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.ReadableType import com.myfatoorah.sdk.v3.entity.MFBoxShadowV3 import com.myfatoorah.sdk.v3.entity.MFCardConfigurationV3 /** * MFCardV3ConfigTranslator - translates the reused JS `MFCardViewStyle` ReadableMap * into the native V3 `MFCardConfigurationV3`. * * The V3 config takes raw ColorInt ints, Float sizes, and a structured MFBoxShadowV3 * (HtmlPageV3 does the px/hex/CSS formatting when it builds the widget config), so this * does NOT reuse the V2 Gson `handleReadableMap` path. Only fields present in the JS * style override the SDK defaults; everything else is left at whatever * MFCardConfigurationV3() defaults to. */ internal object MFCardV3ConfigTranslator { /** * Build an MFCardConfigurationV3 from the JS paymentStyle prop. * * @param style The `MFCardViewStyle` ReadableMap passed from JS, or null for defaults. * @return A configuration with only the JS-supplied fields overridden. */ fun configuration(style: ReadableMap?): MFCardConfigurationV3 { val base = MFCardConfigurationV3() if (style == null) return base //#region Input val inputMap = style.mapOrNull("Input") val input = if (inputMap == null) base.input else { var inp = base.input val phMap = inputMap.mapOrNull("PlaceHolder") inp = inp.copy( color = inputMap.colorOrNull("Color") ?: inp.color, backgroundColor = inputMap.colorOrNull("BackgroundColor") ?: inp.backgroundColor, borderColor = inputMap.colorOrNull("BorderColor") ?: inp.borderColor, fontSize = inputMap.floatOrNull("FontSize") ?: inp.fontSize, fontFamily = inputMap.strOrNull("FontFamily") ?: inp.fontFamily, inputHeight = inputMap.floatOrNull("InputHeight") ?: inp.inputHeight, inputMargin = inputMap.floatOrNull("InputMargin") ?: inp.inputMargin, borderWidth = inputMap.floatOrNull("BorderWidth") ?: inp.borderWidth, borderRadius = inputMap.floatOrNull("BorderRadius") ?: inp.borderRadius, outerRadius = inputMap.floatOrNull("OuterRadius") ?: inp.outerRadius, boxShadow = inputMap.mapOrNull("BoxShadow")?.let { boxShadowV3(it) } ?: inp.boxShadow ) // PlaceHolder.Color lives on MFCardPlaceholderV3 itself, alongside the text fields. if (phMap != null) { inp = inp.copy( placeholder = inp.placeholder.copy( color = phMap.colorOrNull("Color") ?: inp.placeholder.color, holderName = phMap.strOrNull("HolderName") ?: inp.placeholder.holderName, cardNumber = phMap.strOrNull("CardNumber") ?: inp.placeholder.cardNumber, expiryDate = phMap.strOrNull("ExpiryDate") ?: inp.placeholder.expiryDate, securityCode = phMap.strOrNull("SecurityCode") ?: inp.placeholder.securityCode ) ) } inp } //#endregion //#region Label val labelMap = style.mapOrNull("Label") val label = if (labelMap == null) base.label else { var lbl = base.label.copy( display = labelMap.boolOrNull("Display") ?: base.label.display, color = labelMap.colorOrNull("Color") ?: base.label.color, fontSize = labelMap.floatOrNull("FontSize") ?: base.label.fontSize, fontFamily = labelMap.strOrNull("FontFamily") ?: base.label.fontFamily, fontWeight = labelMap.strOrNull("FontWeight") ?: base.label.fontWeight ) val textMap = labelMap.mapOrNull("Text") if (textMap != null) { lbl = lbl.copy( text = lbl.text.copy( holderName = textMap.strOrNull("HolderName") ?: lbl.text.holderName, cardNumber = textMap.strOrNull("CardNumber") ?: lbl.text.cardNumber, expiryDate = textMap.strOrNull("ExpiryDate") ?: lbl.text.expiryDate, securityCode = textMap.strOrNull("SecurityCode") ?: lbl.text.securityCode ) ) } lbl } //#endregion // Error (BorderWidth is ignored by the widget - not mapped) val errorMap = style.mapOrNull("Error") val error = if (errorMap == null) base.error else base.error.copy( borderColor = errorMap.colorOrNull("BorderColor") ?: base.error.borderColor, borderRadius = errorMap.floatOrNull("BorderRadius") ?: base.error.borderRadius, boxShadow = errorMap.mapOrNull("BoxShadow")?.let { boxShadowV3(it) } ?: base.error.boxShadow ) // Saved-card text ("Text" block) - DeleteAlertText nests into MFCardTextV3.deleteAlert val savedCardMap = style.mapOrNull("Text") val text = if (savedCardMap == null) base.text else { val alertMap = savedCardMap.mapOrNull("DeleteAlertText") base.text.copy( saveCard = savedCardMap.strOrNull("SaveCardText") ?: base.text.saveCard, addCard = savedCardMap.strOrNull("AddCardText") ?: base.text.addCard, deleteAlert = base.text.deleteAlert.copy( title = alertMap?.strOrNull("Title") ?: base.text.deleteAlert.title, message = alertMap?.strOrNull("Message") ?: base.text.deleteAlert.message, confirm = alertMap?.strOrNull("Confirm") ?: base.text.deleteAlert.confirm, cancel = alertMap?.strOrNull("Cancel") ?: base.text.deleteAlert.cancel ) ) } // Top-level val direction = style.strOrNull("Direction")?.lowercase() return base.copy( hideCardIcons = style.boolOrNull("HideCardIcons") ?: base.hideCardIcons, showCardholderName = style.boolOrNull("ShowCardholderName") ?: base.showCardholderName, cardHeight = style.floatOrNull("CardHeight") ?: base.cardHeight, tokenHeight = style.floatOrNull("TokenHeight") ?: base.tokenHeight, backgroundColor = style.colorOrNull("BackgroundColor") ?: base.backgroundColor, language = if (direction == "rtl") "ar" else base.language, input = input, label = label, error = error, text = text ) } /** Builds the structured shadow model; MFBoxShadowV3.toString() renders the CSS the widget expects. */ private fun boxShadowV3(map: ReadableMap): MFBoxShadowV3 = MFBoxShadowV3( hOffset = map.numOrNull("HOffset") ?: 0, vOffset = map.numOrNull("VOffset") ?: 0, blur = map.numOrNull("Blur") ?: 0, spread = map.numOrNull("Spread") ?: 0, color = map.colorOrNull("Color") ?: Color.GRAY ) //#region ReadableMap typed accessors (only present, correctly-typed keys override) private fun ReadableMap.boolOrNull(key: String): Boolean? = if (hasKeySafe(key) && getType(key) == ReadableType.Boolean) getBoolean(key) else null private fun ReadableMap.strOrNull(key: String): String? = if (hasKeySafe(key) && getType(key) == ReadableType.String) getString(key) else null private fun ReadableMap.numOrNull(key: String): Int? = if (hasKeySafe(key) && getType(key) == ReadableType.Number) getDouble(key).toInt() else null private fun ReadableMap.floatOrNull(key: String): Float? = if (hasKeySafe(key) && getType(key) == ReadableType.Number) getDouble(key).toFloat() else null private fun ReadableMap.colorOrNull(key: String): Int? = numOrNull(key) private fun ReadableMap.mapOrNull(key: String): ReadableMap? = if (hasKeySafe(key) && getType(key) == ReadableType.Map) getMap(key) else null private fun ReadableMap.hasKeySafe(key: String): Boolean = hasKey(key) && !isNull(key) //#endregion }