package com.checkoutreactnativecomponents.utils import android.graphics.Color import com.checkout.components.interfaces.uicustomisation.BorderRadius import com.checkout.components.interfaces.uicustomisation.designtoken.ColorTokens import com.checkout.components.interfaces.uicustomisation.designtoken.DesignTokens import com.checkout.components.interfaces.uicustomisation.font.* import com.facebook.react.bridge.ReadableMap import androidx.core.graphics.toColorInt public object AppearanceBuilder { public fun fromReadableMap(map: ReadableMap): DesignTokens { val colorTokens = buildColorTokens(map.getMap("colorTokens")) val fonts = buildFonts(map.getMap("fonts")) val borderFormRadius = buildBorderRadiusFromValue(map, "borderFormRadius") val borderButtonRadius = buildBorderRadiusFromValue(map, "borderButtonRadius") return DesignTokens(colorTokens, fonts, borderFormRadius, borderButtonRadius) } internal fun buildColorTokens(map: ReadableMap?): ColorTokens { return ColorTokens( colorDisabled = map?.getColorLong("disabled") ?: Constants.COLOR_DISABLED, colorError = map?.getColorLong("error") ?: Constants.COLOR_ERROR, colorInverse = map?.getColorLong("inverse") ?: Constants.COLOR_INVERSE, colorAction = map?.getColorLong("action") ?: Constants.COLOR_ACTION, colorSuccess = map?.getColorLong("success") ?: Constants.COLOR_SUCCESS, colorPrimary = map?.getColorLong("primary") ?: Constants.COLOR_PRIMARY, colorSecondary = map?.getColorLong("secondary") ?: Constants.COLOR_SECONDARY, colorFormBorder = map?.getColorLong("formBorder") ?: Constants.COLOR_FORM_BORDER, colorBorder = map?.getColorLong("border") ?: Constants.COLOR_BORDER, colorOutline = map?.getColorLong("outline") ?: Constants.COLOR_OUTLINE, colorFormBackground = map?.getColorLong("formBackground") ?: Constants.COLOR_FORM_BACKGROUND, colorBackground = map?.getColorLong("background") ?: Constants.COLOR_BACKGROUND, // This colour doesn't exist on ios colorScrolledContainer = map?.getColorLong("scrolledContainer") ?: Constants.COLOR_SCROLLED_CONTAINER, ) } internal enum class FontSet(val fontName: String) { SANS_SERIF("SansSerif"), SERIF("Serif"), CURSIVE("Cursive"), MONOSPACE("Monospace"), DEFAULT("Default"); companion object { fun fromString(name: String): FontSet { return entries.find { it.fontName.equals(name, ignoreCase = true) } ?: DEFAULT } } } internal fun mapFontFamily(fontFamilyName: String): FontFamily { val fontSet = FontSet.fromString(fontFamilyName) return when (fontSet) { FontSet.SANS_SERIF -> FontFamily.SansSerif FontSet.SERIF -> FontFamily.Serif FontSet.CURSIVE -> FontFamily.Cursive FontSet.MONOSPACE -> FontFamily.Monospace FontSet.DEFAULT -> FontFamily.Default } } internal fun mapFontWeight(fontWeightValue: Int): FontWeight { if (fontWeightValue < 350) { return FontWeight.Light } else if (fontWeightValue < 450 ) { return FontWeight.Normal } else if (fontWeightValue < 550) { return FontWeight.Medium } else if (fontWeightValue < 650) { return FontWeight.SemiBold } else if (fontWeightValue < 750) { return FontWeight.Bold } else { return FontWeight.ExtraBold } } internal fun extractFont(map: ReadableMap?): Font? { if (map != null) { val fontFamilyValue = map.getString("fontFamily") ?: "Default" val fontFamily = mapFontFamily(fontFamilyValue) val fontSize = map.getInt("fontSize") val fontWeight = map.getInt("fontWeight") val letterSpacing = map.getInt("letterSpacing") val lineHeight = map.getInt("lineHeight") return Font( fontFamily = fontFamily, fontSize = fontSize, fontWeight = mapFontWeight(fontWeight), letterSpacing = letterSpacing, lineHeight = lineHeight ) } return null } internal fun buildFonts(map: ReadableMap?): Fonts { return buildMap { extractFont(map?.getMap("button"))?.let { put(FontName.Button, it) } extractFont(map?.getMap("input"))?.let { put(FontName.Input, it) } extractFont(map?.getMap("footnote"))?.let { put(FontName.Footnote, it) } extractFont(map?.getMap("label"))?.let { put(FontName.Label, it) } extractFont(map?.getMap("subheading"))?.let { put(FontName.Subheading, it) } } } internal fun buildBorderRadiusFromValue(map: ReadableMap, key: String): BorderRadius { if (!map.hasKey(key) || map.isNull(key)) { return BorderRadius(0) } val radius = map.getInt(key) return BorderRadius(radius) } internal fun parseColorString(colorString: String): Int { val trimmed = colorString.trim() val lower = trimmed.lowercase() return when { lower.startsWith("rgb(") -> { val values = trimmed.substring(trimmed.indexOf('(') + 1, trimmed.lastIndexOf(')')).split(",") if (values.size != 3) throw IllegalArgumentException("Invalid rgb color") val r = values[0].trim().toInt() val g = values[1].trim().toInt() val b = values[2].trim().toInt() Color.rgb(r, g, b) } lower.startsWith("rgba(") -> { val values = trimmed.substring(trimmed.indexOf('(') + 1, trimmed.lastIndexOf(')')).split(",") if (values.size != 4) throw IllegalArgumentException("Invalid rgba color") val r = values[0].trim().toInt() val g = values[1].trim().toInt() val b = values[2].trim().toInt() val aFloat = values[3].trim().toFloat() val a = (aFloat * 255).toInt() Color.argb(a, r, g, b) } else -> { trimmed.toColorInt() } } } private fun ReadableMap.getColorLong(key: String): Long? { if (!hasKey(key) || isNull(key)) return null val colorString = getString(key) ?: return null return try { val colorInt = parseColorString(colorString) colorInt.toLong() and 0xFFFFFFFFL } catch (e: IllegalArgumentException) { null } } }