diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListView.kt index d9ca67b0c..0fac35af1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListView.kt @@ -370,7 +370,8 @@ fun ServerConfigClickableLine( ) { Column(Modifier.clickable(onClick = onClick)) { RenderRelayIcon( - iconUrl = item.briefInfo.favIcon, + item.briefInfo.displayUrl, + item.briefInfo.favIcon, loadProfilePicture, MaterialTheme.colorScheme.largeRelayIconModifier ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelayInformationDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelayInformationDialog.kt index f109b3c07..e764259cd 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelayInformationDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelayInformationDialog.kt @@ -82,6 +82,7 @@ fun RelayInformationDialog( Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center, modifier = StdPadding.fillMaxWidth()) { Column() { RenderRelayIcon( + relayBriefInfo.displayUrl, relayBriefInfo.favIcon, automaticallyShowProfilePicture, MaterialTheme.colorScheme.largeRelayIconModifier diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/Robohash.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/Robohash.kt index ce22d7c3f..95f1803f4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/Robohash.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/Robohash.kt @@ -2,10 +2,7 @@ package com.vitorpamplona.amethyst.ui.components import android.content.Context import android.net.Uri -import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.toArgb import coil.ImageLoader import coil.decode.DataSource import coil.decode.ImageSource @@ -14,308 +11,48 @@ import coil.fetch.SourceResult import coil.request.ImageRequest import coil.request.Options import com.vitorpamplona.amethyst.service.checkNotInMainThread -import com.vitorpamplona.quartz.crypto.CryptoUtils -import com.vitorpamplona.quartz.encoders.toHexKey +import com.vitorpamplona.quartz.utils.Robohash import okio.Buffer - -private fun toHex(color: Color): String { - val argb = color.toArgb() - val rgb = argb and 0x00FFFFFF // Mask out the alpha channel - return String.format("#%06X", rgb) -} - -private fun byteMod10(byte: Byte): Int { - val ub = byte.toUByte().toInt() - return ub % 10 -} - -private fun bytesToRGB(b1: Byte, b2: Byte, b3: Byte): Color { - return Color(b1.toUByte().toInt(), b2.toUByte().toInt(), b3.toUByte().toInt()) -} - -private fun svgString(msg: String): String { - checkNotInMainThread() - - val hash = CryptoUtils.sha256(msg.toByteArray()) - val hashHex = hash.toHexKey() - val bgColor = bytesToRGB(hash[0], hash[1], hash[2]) - val fgColor = bytesToRGB(hash[3], hash[4], hash[5]) - val bodyIndex = byteMod10(hash[6]) - val faceIndex = byteMod10(hash[7]) - val eyesIndex = byteMod10(hash[8]) - val mouthIndex = byteMod10(hash[9]) - val accIndex = byteMod10(hash[10]) - val body = bodies[bodyIndex] - val face = faces[faceIndex] - val eye = eyes[eyesIndex] - val mouth = mouths[mouthIndex] - val accessory = accessories[accIndex] - - return """ - - - - Robohash $hashHex - ${background}${body.paths}${face.paths}${eye.paths}${mouth.paths}${accessory.paths} -""" -} +import java.nio.charset.Charset @Stable class HashImageFetcher( private val context: Context, + private val isLightTheme: Boolean, private val data: Uri ) : Fetcher { override suspend fun fetch(): SourceResult { checkNotInMainThread() val source = try { - Buffer().apply { write(svgString(data.toString()).toByteArray()) } + val buffer = Buffer() + buffer.writeString(Robohash.assemble(data.toString(), isLightTheme), Charset.defaultCharset()) + buffer } finally { } return SourceResult( source = ImageSource(source, context), - mimeType = null, + mimeType = "image/svg+xml", dataSource = DataSource.MEMORY ) } object Factory : Fetcher.Factory { override fun create(data: Uri, options: Options, imageLoader: ImageLoader): Fetcher { - return HashImageFetcher(options.context, data) + return HashImageFetcher(options.context, options.parameters.value("isLightTheme") ?: true, data) } } } -object Robohash { - fun imageRequest(context: Context, message: String): ImageRequest { + +object RobohashImageRequest { + fun build(context: Context, message: String, isLightTheme: Boolean): ImageRequest { return ImageRequest .Builder(context) - .data("robohash:$message") + .data(message) .fetcherFactory(HashImageFetcher.Factory) + .setParameter("isLightTheme", isLightTheme) .addHeader("Cache-Control", "max-age=31536000") .build() } } - -@Immutable -private data class Part(val style: String, val paths: String) - -private const val background = """""" - -private val accessories: List = listOf( - Part( - """.cls-00-2{fill:none;}.cls-00-2,.cls-00-3,.cls-00-4{stroke:#000;}.cls-00-2,.cls-00-3{stroke-linecap:round;stroke-linejoin:round;}.cls-00-3{fill-opacity:0.4;stroke-width:0.75px;}.cls-00-4{fill-opacity:0.2;stroke-miterlimit:10;stroke-width:0.5px;}""", - """""" - ), - Part( - """.cls-01-2{fill:#fff;fill-opacity:0.2;}.cls-01-3,.cls-01-4{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-01-4{stroke-width:0.75px;}""", - """""" - ), - Part( - """.cls-02-2{fill:#be1e2d;}.cls-02-11,.cls-02-2,.cls-02-5,.cls-02-6,.cls-02-7,.cls-02-9{stroke:#000;}.cls-02-11,.cls-02-2,.cls-02-5,.cls-02-9{stroke-miterlimit:10;}.cls-02-3{fill:#561317;}.cls-02-4{fill:#ed293b;}.cls-02-11,.cls-02-5,.cls-02-7{fill:none;}.cls-02-5,.cls-02-6{stroke-width:0.75px;}.cls-02-6{fill:#fff;}.cls-02-6,.cls-02-8{fill-opacity:0.2;}.cls-02-6,.cls-02-7{stroke-linecap:round;stroke-linejoin:round;}.cls-02-9{fill:#e6e7e8;}.cls-02-10{fill:#d0d2d3;}""", - """""" - ), - Part( - """.cls-03-2,.cls-03-3,.cls-03-8{fill:#fff;}.cls-03-2,.cls-03-3,.cls-03-7{fill-opacity:0.2;}.cls-03-2,.cls-03-4,.cls-03-5{stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-03-2{stroke-width:0.75px;}.cls-03-4{fill:none;}.cls-03-5{fill:#ec1c24;}.cls-03-6{fill-opacity:0.1;}.cls-03-8{fill-opacity:0.4;}""", - """""" - ), - Part( - """.cls-04-2,.cls-04-3{fill:none;stroke-linecap:round;stroke-linejoin:round;}.cls-04-2,.cls-04-3,.cls-04-5{stroke:#000;}.cls-04-3,.cls-04-5{stroke-width:0.75px;}.cls-04-4,.cls-04-6{fill:#fff;}.cls-04-4{fill-opacity:0.4;}.cls-04-5{fill:#ec1c24;stroke-miterlimit:10;}.cls-04-6,.cls-04-7{fill-opacity:0.2;}""", - """""" - ), - Part( - """.cls-05-2{fill:#fff;fill-opacity:0.4;}.cls-05-2,.cls-05-3{stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-05-3{fill:none;stroke-width:0.75px;}""", - """""" - ), - Part( - """.cls-06-2,.cls-06-4{fill-opacity:0.2;}.cls-06-3,.cls-06-6{fill:none;}.cls-06-3,.cls-06-5,.cls-06-6{stroke:#000;}.cls-06-3,.cls-06-5{stroke-miterlimit:10;}.cls-06-4{fill:#fff;}.cls-06-5{fill:#ec1c24;stroke-width:0.75px;}.cls-06-6{stroke-linecap:round;stroke-linejoin:round;}""", - """""" - ), - Part( - """.cls-07-10,.cls-07-2,.cls-07-4,.cls-07-8,.cls-07-9{fill:none;}.cls-07-10,.cls-07-2,.cls-07-3,.cls-07-4,.cls-07-5,.cls-07-6,.cls-07-8,.cls-07-9{stroke:#000;}.cls-07-2,.cls-07-3,.cls-07-4,.cls-07-5,.cls-07-6,.cls-07-8{stroke-linecap:round;}.cls-07-2,.cls-07-3,.cls-07-4,.cls-07-5,.cls-07-6,.cls-07-8,.cls-07-9{stroke-linejoin:round;}.cls-07-3,.cls-07-5{fill:#fff;}.cls-07-3,.cls-07-5,.cls-07-6,.cls-07-7{fill-opacity:0.2;}.cls-07-3,.cls-07-4{stroke-width:0.75px;}.cls-07-10,.cls-07-8,.cls-07-9{stroke-width:1.5px;}.cls-07-10{stroke-miterlimit:10;}""", - """""" - ), - Part( - """.cls-08-2{fill:none;}.cls-08-2,.cls-08-3,.cls-08-5,.cls-08-6,.cls-08-7{stroke:#000;stroke-miterlimit:10;}.cls-08-3,.cls-08-4{fill:#fff;}.cls-08-3,.cls-08-4,.cls-08-8{fill-opacity:0.2;}.cls-08-5{fill:#716558;}.cls-08-6{fill:#9a8479;}.cls-08-7{fill:#c1b49a;}""", - """""" - ), - Part( - """.cls-09-2{fill:none;}.cls-09-2,.cls-09-4{stroke:#000;stroke-miterlimit:10;}.cls-09-3,.cls-09-4{fill:#fff;}.cls-09-3{fill-opacity:0.2;}.cls-09-5{fill:#d0d2d3;}.cls-09-6{fill-opacity:0.4;}""", - """""" - ) -) - -private val bodies: List = listOf( - Part( - """.cls-10-2{fill:#fff;fill-opacity:0.4;}.cls-10-3,.cls-10-5,.cls-10-7{fill:none;}.cls-10-3,.cls-10-4,.cls-10-5,.cls-10-6,.cls-10-7,.cls-10-8{stroke:#000;stroke-miterlimit:10;}.cls-10-3{stroke-width:1.2px;}.cls-10-4,.cls-10-6,.cls-10-9{fill-opacity:0.2;}.cls-10-5{stroke-width:1.25px;}.cls-10-6{stroke-width:0.75px;}.cls-10-8{fill:#6d6e70;}""", - """""" - ), - Part( - """.cls-11-2{fill:#fff;}.cls-11-2,.cls-11-6{fill-opacity:0.2;}.cls-11-3{fill:none;}.cls-11-3,.cls-11-4{stroke:#000;stroke-miterlimit:10;}.cls-11-4{fill:#6d6e70;}.cls-11-5{opacity:0.2;}""", - """""" - ), - Part( - """.cls-12-2{fill:#fff;fill-opacity:0.4;}.cls-12-3,.cls-12-7{fill:none;}.cls-12-3,.cls-12-5,.cls-12-6,.cls-12-7{stroke:#000;}.cls-12-3,.cls-12-5,.cls-12-6{stroke-miterlimit:10;}.cls-12-4,.cls-12-6{fill-opacity:0.2;}.cls-12-5{fill:#6d6e70;}.cls-12-7{stroke-linecap:round;stroke-linejoin:round;}""", - """""" - ), - Part( - """.cls-13-2{fill:none;}.cls-13-2,.cls-13-5{stroke:#000;stroke-miterlimit:10;}.cls-13-3{fill:#fff;fill-opacity:0.4;}.cls-13-4{fill-opacity:0.2;}.cls-13-5{fill:#6d6e70;}""", - """""" - ), - Part( - """.cls-14-2{fill:none;}.cls-14-2,.cls-14-4{stroke:#000;stroke-miterlimit:10;}.cls-14-3,.cls-14-6{fill-opacity:0.2;}.cls-14-4{fill:#6d6e70;}.cls-14-5,.cls-14-6{fill:#fff;}.cls-14-5{fill-opacity:0.4;}""", - """""" - ), - Part( - """.cls-15-2{fill:#fff;}.cls-15-2,.cls-15-4{fill-opacity:0.2;}.cls-15-2,.cls-15-3,.cls-15-5{stroke:#000;stroke-miterlimit:10;}.cls-15-3{fill:none;}.cls-15-5{fill:#6d6e70;}""", - """""" - ), - Part( - """.cls-16-2,.cls-16-5{fill:none;}.cls-16-2,.cls-16-5,.cls-16-6,.cls-16-7,.cls-16-8,.cls-16-9{stroke:#000;}.cls-16-2{stroke-linecap:round;stroke-linejoin:round;}.cls-16-3,.cls-16-6,.cls-16-7{fill-opacity:0.2;}.cls-16-4,.cls-16-6{fill:#fff;}.cls-16-4{fill-opacity:0.4;}.cls-16-5,.cls-16-6,.cls-16-7,.cls-16-8,.cls-16-9{stroke-miterlimit:10;}.cls-16-8{fill:#f9ec31;}.cls-16-9{fill:#6d6e70;}""", - """""" - ), - Part( - """.cls-17-2{fill:#020202;}.cls-17-2,.cls-17-4,.cls-17-6,.cls-17-7{fill-opacity:0.4;}.cls-17-3{fill-opacity:0.2;}.cls-17-4,.cls-17-6{fill:#fff;}.cls-17-4,.cls-17-5,.cls-17-8{stroke:#000;stroke-miterlimit:10;}.cls-17-5{fill:none;}.cls-17-8{fill:#6d6e70;}""", - """""" - ), - Part( - """.cls-fill-2,.cls-18-3,.cls-18-5,.cls-18-6{stroke:#000;stroke-miterlimit:10;}.cls-18-3{fill:none;}.cls-18-4{fill:#fff;}.cls-18-4,.cls-18-5{fill-opacity:0.4;}.cls-18-6{fill:#6d6e70;}.cls-18-7{opacity:0.2;}.cls-18-8{fill-opacity:0.2;}""", - """""" - ), - Part( - """.cls-19-2{fill-opacity:0.6;}.cls-19-11,.cls-19-13,.cls-19-14,.cls-19-2,.cls-19-3,.cls-19-4,.cls-19-6,.cls-19-8{stroke:#000;}.cls-19-11,.cls-19-13,.cls-19-2,.cls-19-4,.cls-19-6,.cls-19-8{stroke-miterlimit:10;}.cls-19-11,.cls-19-14,.cls-19-3,.cls-19-8{fill:none;}.cls-19-14,.cls-19-3{stroke-linecap:round;stroke-linejoin:round;}.cls-19-10,.cls-19-12,.cls-19-4,.cls-19-5{fill:#fff;}.cls-19-12,.cls-19-4{fill-opacity:0.2;}.cls-19-4{stroke-opacity:0;}.cls-19-5{fill-opacity:0.1;}.cls-19-6{fill:#6d6e70;}.cls-19-7{fill:#58595b;}.cls-19-13,.cls-19-9{fill-opacity:0.4;}.cls-19-10{fill-opacity:0.5;}.cls-19-11,.cls-19-14{stroke-width:0.75px;}""", - """""" - ) -) - -private val eyes: List = listOf( - Part( - """.cls-20-2{fill-opacity:0.4;}.cls-20-2,.cls-20-3{stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-20-3{fill:#461917;stroke-width:0.5px;}""", - """""" - ), - Part( - """.cls-21-2{fill-opacity:0.2;}.cls-21-2,.cls-21-3,.cls-21-4,.cls-21-5{stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-21-2,.cls-21-4,.cls-21-5{stroke-width:0.5px;}.cls-21-3,.cls-21-4{fill-opacity:0.4;}.cls-21-5{fill:#461917;}.cls-21-6{fill:#faaf40;}""", - """""" - ), - Part( - """.cls-22-2{opacity:0.4;}.cls-22-3{fill:#461917;}.cls-22-3,.cls-22-4,.cls-22-5{stroke:#000;}.cls-22-3,.cls-22-5{stroke-linecap:round;stroke-linejoin:round;}.cls-22-3,.cls-22-4{stroke-width:0.5px;}.cls-22-4{fill:#ec1c24;stroke-miterlimit:10;}.cls-22-5{fill:none;stroke-width:0.75px;}""", - """""" - ), - Part( - """.cls-23-2,.cls-23-3{fill:#fff;}.cls-23-2{fill-opacity:0.4;}.cls-23-3{fill-opacity:0.2;}.cls-23-3,.cls-23-4,.cls-23-5{stroke:#000;stroke-miterlimit:10;}.cls-23-4{fill:none;}.cls-23-4,.cls-23-5{stroke-width:0.75px;}.cls-23-5{fill:red;}""", - """""" - ), - Part( - """.cls-24-2{fill-opacity:0.4;stroke-miterlimit:10;}.cls-24-2,.cls-24-3,.cls-24-5{stroke:#000;}.cls-24-2,.cls-24-5{stroke-width:0.75px;}.cls-24-3,.cls-24-5{fill:#461917;stroke-linecap:round;stroke-linejoin:round;}.cls-24-3{stroke-width:0.5px;}.cls-24-4{fill:#ec1c24;}""", - """""" - ), - Part( - """.cls-25-2{fill-opacity:0.55;stroke-miterlimit:10;stroke-width:0.75px;}.cls-25-2,.cls-25-3{stroke:#000;}.cls-25-3{fill:#461917;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5px;}.cls-25-4{fill:#ec1c24;}""", - """""" - ), - Part( - """.cls-26-2{fill-opacity:0.6;}.cls-26-2,.cls-26-3,.cls-26-4,.cls-26-5,.cls-26-6{stroke:#000;}.cls-26-2,.cls-26-3,.cls-26-4,.cls-26-6{stroke-linecap:round;stroke-linejoin:round;}.cls-26-3{fill:#461917;}.cls-26-3,.cls-26-4,.cls-26-5{stroke-width:0.5px;}.cls-26-4,.cls-26-5{fill:#f9ec31;}.cls-26-5{stroke-miterlimit:10;}.cls-26-6{fill:none;}""", - """""" - ), - Part( - """.cls-27-2{fill-opacity:0.4;stroke-miterlimit:10;}.cls-27-2,.cls-27-3,.cls-27-4{stroke:#000;}.cls-27-3,.cls-27-4{fill:#461917;stroke-linecap:round;stroke-linejoin:round;}.cls-27-3{stroke-width:0.5px;}.cls-27-4{stroke-width:0.75px;}""", - """""" - ), - Part( - """.cls-28-2{fill:none;}.cls-28-2,.cls-28-3,.cls-28-4,.cls-28-6{stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-28-2,.cls-28-4,.cls-28-6{stroke-width:0.75px;}.cls-28-3{fill:#461917;stroke-width:0.5px;}.cls-28-4{fill-opacity:0.4;}.cls-28-5{fill:#fff100;}.cls-28-6{fill:#fff;fill-opacity:0.2;}""", - """""" - ), - Part( - """.cls-29-2{fill:#fff;}.cls-29-2,.cls-29-4{fill-opacity:0.4;}.cls-29-3{fill:none;}.cls-29-3,.cls-29-4{stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-29-4{stroke-width:0.75px;}""", - """""" - ) -) - -private val faces: List = listOf( - Part( - """.cls-30-2{fill:#fff;fill-opacity:0.4;}.cls-30-3,.cls-30-4{fill:none;}.cls-30-3,.cls-30-4,.cls-30-6{stroke:#000;}.cls-30-3,.cls-30-6{stroke-linecap:round;stroke-linejoin:round;}.cls-30-4{stroke-miterlimit:10;}.cls-30-5,.cls-30-6{fill-opacity:0.2;}.cls-30-6{stroke-width:0.75px;}""", - """""" - ), - Part( - """.cls-31-2,.cls-31-4{fill-opacity:0.2;}.cls-31-3{fill:none;}.cls-31-3,.cls-31-4{stroke:#000;stroke-miterlimit:10;}.cls-31-4,.cls-31-5{fill:#fff;}.cls-31-4{stroke-width:0.75px;}.cls-31-5{fill-opacity:0.4;}""", - """""" - ), - Part( - """.cls-32-2{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-32-3{fill-opacity:0.2;}.cls-32-4{fill:#fff;fill-opacity:0.4;}""", - """""" - ), - Part( - """.cls-33-2{fill-opacity:0.2;}.cls-33-3{fill:#fff;fill-opacity:0.4;}.cls-33-4{fill:none;stroke:#000;stroke-miterlimit:10;}""", - """""" - ), - Part( - """.cls-34-2,.cls-34-3{fill:#fff;}.cls-34-2{fill-opacity:0.4;}.cls-34-3,.cls-34-5{fill-opacity:0.2;}.cls-34-3,.cls-34-4{stroke:#000;stroke-miterlimit:10;}.cls-34-4{fill:none;}""", - """""" - ), - Part( - """.cls-35-2{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-35-3{fill-opacity:0.2;}.cls-35-4{fill:#fff;fill-opacity:0.4;}""", - """""" - ), - Part( - """.cls-36-2,.cls-36-6{fill:#fff;}.cls-36-2{fill-opacity:0.4;}.cls-36-3,.cls-36-4{fill:none;stroke:#000;stroke-miterlimit:10;}.cls-36-3{stroke-width:2px;}.cls-36-5,.cls-36-6{fill-opacity:0.2;}""", - """""" - ), - Part( - """.cls-37-2{fill:#fff;fill-opacity:0.4;}.cls-37-3{fill:none;}.cls-37-3,.cls-37-4{stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-37-4{fill-opacity:0.2;}""", - """""" - ), - Part( - """.cls-38-2{fill:none;stroke:#000;stroke-miterlimit:10;}.cls-38-3,.cls-38-6{fill:#fff;}.cls-38-3,.cls-38-4{fill-opacity:0.2;}.cls-38-5{fill-opacity:0.1;}.cls-38-6{fill-opacity:0.4;}""", - """""" - ), - Part( - """.cls-39-2{fill:#fff;fill-opacity:0.4;}.cls-39-3{fill:none;}.cls-39-3,.cls-39-4{stroke:#000;stroke-miterlimit:10;}.cls-39-4{fill-opacity:0.2;stroke-width:0.75px;}""", - """""" - ) -) - -private val mouths: List = listOf( - Part( - """.cls-40-2{fill-opacity:0.4;stroke:#000;stroke-linecap:round;stroke-linejoin:round;}""", - """""" - ), - Part( - """.cls-41-2,.cls-41-4{fill:none;}.cls-41-2,.cls-41-3,.cls-41-4{stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-41-3{fill-opacity:0.4;}.cls-41-3,.cls-41-4{stroke-width:0.75px;}""", - """""" - ), - Part( - """.cls-42-2{fill-opacity:0.4;}.cls-42-2,.cls-42-5,.cls-42-6{stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-42-3{fill:#ee4036;}.cls-42-4{fill:#f05a28;}.cls-42-5{fill:#faaf40;stroke-width:0.75px;}.cls-42-6{fill:none;}""", - """""" - ), - Part( - """.cls-43-2{fill:#f9ec31;}.cls-43-3{fill:#faaf40;}.cls-43-4,.cls-43-6{fill:none;}.cls-43-4,.cls-43-5,.cls-43-6{stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-43-5{fill-opacity:0.4;}.cls-43-5,.cls-43-6{stroke-width:0.75px;}""", - """""" - ), - Part( - """.cls-44-2{fill:none;}.cls-44-2,.cls-44-5{stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-44-3{opacity:0.4;}.cls-44-4{fill:#461917;}.cls-44-5{fill-opacity:0.4;stroke-width:0.75px;}""", - """""" - ), - Part( - """.cls-45-2{fill-opacity:0.4;stroke-miterlimit:10;}.cls-45-2,.cls-45-3{stroke:#000;}.cls-45-3{fill:#461917;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5px;}""", - """""" - ), - Part( - """.cls-46-2{fill-opacity:0.4;}.cls-46-3{fill:none;}.cls-46-3,.cls-46-4{stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-46-4{fill:#461917;stroke-width:0.5px;}""", - """""" - ), - Part( - """.cls-47-2{fill-opacity:0.4;}.cls-47-2,.cls-47-3,.cls-47-4,.cls-47-5{stroke:#000;stroke-miterlimit:10;}.cls-47-3{fill:#f6921e;}.cls-47-4{fill:#f9ec31;stroke-width:0.75px;}.cls-47-5{fill:none;}""", - """""" - ), - Part( - """.cls-48-2{opacity:0.4;}.cls-48-3,.cls-48-4,.cls-48-5{fill:none;}.cls-48-3,.cls-48-4,.cls-48-5,.cls-48-6,.cls-48-7,.cls-48-8{stroke:#000;}.cls-48-3,.cls-48-5,.cls-48-7{stroke-miterlimit:10;}.cls-48-4,.cls-48-6,.cls-48-8{stroke-linecap:round;stroke-linejoin:round;}.cls-48-4,.cls-48-5{stroke-width:0.5px;}.cls-48-6{fill:#f6921e;stroke-width:1.2px;}.cls-48-7{fill:#f9ec31;}.cls-48-7,.cls-48-8{stroke-width:0.75px;}.cls-48-8{fill:#f05a27;}""", - """""" - ), - Part( - """.cls-49-2{fill:none;}.cls-49-2,.cls-49-3,.cls-49-5{stroke:#000;}.cls-49-2,.cls-49-3{stroke-linecap:round;stroke-linejoin:round;}.cls-49-3{opacity:0.1;}.cls-49-4{opacity:0.4;}.cls-49-5{fill-opacity:0.4;stroke-miterlimit:10;stroke-width:0.75px;}""", - """""" - ) -) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt index 6c9c70e82..80c544275 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt @@ -4,6 +4,7 @@ import android.content.Context import android.graphics.BitmapFactory import android.net.Uri import androidx.compose.foundation.Image +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.derivedStateOf @@ -29,6 +30,7 @@ import coil.fetch.Fetcher import coil.request.ImageRequest import coil.request.Options import com.vitorpamplona.amethyst.service.checkNotInMainThread +import com.vitorpamplona.amethyst.ui.theme.isLight import java.util.Base64 @Composable @@ -45,9 +47,10 @@ fun RobohashAsyncImage( filterQuality: FilterQuality = DrawScope.DefaultFilterQuality ) { val context = LocalContext.current + val isLightTheme = MaterialTheme.colorScheme.isLight val imageRequest = remember(robot) { - Robohash.imageRequest(context, robot) + RobohashImageRequest.build(context, robot, isLightTheme) } AsyncImage( @@ -78,8 +81,9 @@ fun RobohashFallbackAsyncImage( loadProfilePicture: Boolean ) { val context = LocalContext.current + val isLightTheme = MaterialTheme.colorScheme.isLight val painter = rememberAsyncImagePainter( - model = Robohash.imageRequest(context, robot) + model = RobohashImageRequest.build(context, robot, isLightTheme) ) if (model != null && loadProfilePicture) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AccountSwitchBottomSheet.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AccountSwitchBottomSheet.kt index d2d036022..875da1c06 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AccountSwitchBottomSheet.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AccountSwitchBottomSheet.kt @@ -221,7 +221,7 @@ private fun AccountPicture(user: User, loadProfilePicture: Boolean) { val profilePicture by user.live().profilePictureChanges.observeAsState() RobohashFallbackAsyncImage( - robot = remember(user) { user.pubkeyHex }, + robot = user.pubkeyHex, model = profilePicture, contentDescription = stringResource(R.string.profile_image), modifier = AccountPictureModifier, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt index e2cdf1ea5..36134ed90 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt @@ -169,18 +169,19 @@ fun RenderRelay(relay: RelayBriefInfoCache.RelayBriefInfo, accountViewModel: Acc modifier = clickableModifier, contentAlignment = Alignment.Center ) { - RenderRelayIcon(relay.favIcon, automaticallyShowProfilePicture) + RenderRelayIcon(relay.displayUrl, relay.favIcon, automaticallyShowProfilePicture) } } @Composable fun RenderRelayIcon( + displayUrl: String, iconUrl: String, loadProfilePicture: Boolean, iconModifier: Modifier = MaterialTheme.colorScheme.relayIconModifier ) { RobohashFallbackAsyncImage( - robot = iconUrl, + robot = displayUrl, model = iconUrl, contentDescription = stringResource(id = R.string.relay_icon), colorFilter = RelayIconFilter, diff --git a/benchmark/src/androidTest/java/com/vitorpamplona/amethyst/benchmark/RobohashBenchmark.kt b/benchmark/src/androidTest/java/com/vitorpamplona/amethyst/benchmark/RobohashBenchmark.kt new file mode 100644 index 000000000..67cd6a036 --- /dev/null +++ b/benchmark/src/androidTest/java/com/vitorpamplona/amethyst/benchmark/RobohashBenchmark.kt @@ -0,0 +1,38 @@ +package com.vitorpamplona.amethyst.benchmark + +import androidx.benchmark.junit4.BenchmarkRule +import androidx.benchmark.junit4.measureRepeated +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.vitorpamplona.quartz.encoders.Hex +import com.vitorpamplona.quartz.utils.Robohash +import junit.framework.TestCase.assertEquals +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import java.lang.StringBuilder + +/** + * Benchmark, which will execute on an Android device. + * + * The body of [BenchmarkRule.measureRepeated] is measured in a loop, and Studio will + * output the result. Modify your code to see how it affects performance. + */ +@RunWith(AndroidJUnit4::class) +class RobohashBenchmark { + + @get:Rule + val benchmarkRule = BenchmarkRule() + + val WarmHex = "f4f016c739b8ec0d6313540a8b12cf48a72b485d38338627ec9d427583551f9a" + val TestHex = "48a72b485d38338627ec9d427583551f9af4f016c739b8ec0d6313540a8b12cf" + + @Test + fun createSVG() { + // warm up + Robohash.assemble(WarmHex, true) + benchmarkRule.measureRepeated { + val result = Robohash.assemble(TestHex, true) + assertEquals("""""", result) + } + } +} \ No newline at end of file diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/encoders/HexUtils.kt b/quartz/src/main/java/com/vitorpamplona/quartz/encoders/HexUtils.kt index a219da8d3..cfead27e4 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/encoders/HexUtils.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/encoders/HexUtils.kt @@ -23,7 +23,8 @@ object HexValidator { if (hex == null) return false if (hex.length % 2 != 0) return false // must be even var isHex = true - for (c in hex.toCharArray()) { + + for (c in hex) { if (!isHexChar(c)) { isHex = false break @@ -34,7 +35,7 @@ object HexValidator { } object Hex { - private val hexCode = arrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f') + val hexCode = arrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f') // Faster if no calculations are needed. private fun hexToBin(ch: Char): Int = when (ch) { diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/Robohash.kt b/quartz/src/main/java/com/vitorpamplona/quartz/utils/Robohash.kt new file mode 100644 index 000000000..aa7e0404c --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/utils/Robohash.kt @@ -0,0 +1,325 @@ +package com.vitorpamplona.quartz.utils + +import android.util.Log +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.crypto.CryptoUtils +import com.vitorpamplona.quartz.encoders.Hex +import com.vitorpamplona.quartz.encoders.HexValidator +import java.lang.StringBuilder + +object Robohash { + private fun encodeColor(r: Int, g: Int, b: Int): String { + return String(CharArray(7) { + when (it) { + 0 -> '#' + 1 -> Hex.hexCode[(r shr 4) and 0xF] + 2 -> Hex.hexCode[r and 0xF] + 3 -> Hex.hexCode[(g shr 4) and 0xF] + 4 -> Hex.hexCode[g and 0xF] + 5 -> Hex.hexCode[(b shr 4) and 0xF] + 6 -> Hex.hexCode[b and 0xF] + else -> ' ' + } + }) + } + + private fun byteMod10(byte: Byte): Int { + return byte.toUByte().toInt() % 10 + } + + private fun reduce(start: Int, channel: Byte): Int { + return (start + (channel.toUByte().toInt() * 0.3906f)).toInt() + } + + private fun bytesToRGB(r: Byte, g: Byte, b: Byte, makeLight: Boolean): String { + return if (makeLight) + // > 150-256 color channels + encodeColor(reduce(150, r), reduce(150, g), reduce(150, b)) + else + // < 50-100 color channels + encodeColor(reduce(50, r), reduce(50, g), reduce(50, b)) + } + + fun assemble(msg: String, isLightTheme: Boolean): String { + val hash = if (HexValidator.isHex(msg)) { + Hex.decode(msg) + } else { + Log.w("Robohash", "$msg is not a hex") + CryptoUtils.sha256(msg.toByteArray()) + } + val bgColor = bytesToRGB(hash[0], hash[1], hash[2], isLightTheme) + val fgColor = bytesToRGB(hash[3], hash[4], hash[5], !isLightTheme) + val body = bodies[byteMod10(hash[6])] + val face = faces[byteMod10(hash[7])] + val eye = eyes[byteMod10(hash[8])] + val mouth = mouths[byteMod10(hash[9])] + val accessory = accessories[byteMod10(hash[10])] + + val capacity = header.length + 74 + + body.style.length + body.paths.length + + face.style.length + face.paths.length + + eye.style.length + eye.paths.length + + mouth.style.length + mouth.paths.length + + accessory.style.length + accessory.paths.length + + mid.length + background.length + end.length + + val result = StringBuilder(capacity) + + result.append(header) + + result.append(".cls-bg{fill:") + result.append(bgColor) + result.append(";}.cls-fill-1{fill:") + result.append(fgColor) + result.append(";}.cls-fill-2{fill:") + result.append(fgColor) + result.append(";}") + + result.append(body.style) + result.append(face.style) + result.append(eye.style) + result.append(mouth.style) + result.append(accessory.style) + + result.append(mid) + + result.append(background) + result.append(body.paths) + result.append(face.paths) + result.append(eye.paths) + result.append(mouth.paths) + result.append(accessory.paths) + + result.append(end) + + val resultStr = result.toString() + check(resultStr.length == capacity) { + "${resultStr.length} was different from $capacity" + } + return resultStr + } + + @Immutable + private data class Part(val style: String, val paths: String) + + const val header = "" + const val end = "" + + private const val background = """""" + + private val accessories: List = listOf( + Part( + """.cls-00-2{fill:none;}.cls-00-2,.cls-00-3,.cls-00-4{stroke:#000;}.cls-00-2,.cls-00-3{stroke-linecap:round;stroke-linejoin:round;}.cls-00-3{fill-opacity:0.4;stroke-width:0.75px;}.cls-00-4{fill-opacity:0.2;stroke-miterlimit:10;stroke-width:0.5px;}""", + """""" + ), + Part( + """.cls-01-2{fill:#fff;fill-opacity:0.2;}.cls-01-3,.cls-01-4{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-01-4{stroke-width:0.75px;}""", + """""" + ), + Part( + """.cls-02-2{fill:#be1e2d;}.cls-02-11,.cls-02-2,.cls-02-5,.cls-02-6,.cls-02-7,.cls-02-9{stroke:#000;}.cls-02-11,.cls-02-2,.cls-02-5,.cls-02-9{stroke-miterlimit:10;}.cls-02-3{fill:#561317;}.cls-02-4{fill:#ed293b;}.cls-02-11,.cls-02-5,.cls-02-7{fill:none;}.cls-02-5,.cls-02-6{stroke-width:0.75px;}.cls-02-6{fill:#fff;}.cls-02-6,.cls-02-8{fill-opacity:0.2;}.cls-02-6,.cls-02-7{stroke-linecap:round;stroke-linejoin:round;}.cls-02-9{fill:#e6e7e8;}.cls-02-10{fill:#d0d2d3;}""", + """""" + ), + Part( + """.cls-03-2,.cls-03-3,.cls-03-8{fill:#fff;}.cls-03-2,.cls-03-3,.cls-03-7{fill-opacity:0.2;}.cls-03-2,.cls-03-4,.cls-03-5{stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-03-2{stroke-width:0.75px;}.cls-03-4{fill:none;}.cls-03-5{fill:#ec1c24;}.cls-03-6{fill-opacity:0.1;}.cls-03-8{fill-opacity:0.4;}""", + """""" + ), + Part( + """.cls-04-2,.cls-04-3{fill:none;stroke-linecap:round;stroke-linejoin:round;}.cls-04-2,.cls-04-3,.cls-04-5{stroke:#000;}.cls-04-3,.cls-04-5{stroke-width:0.75px;}.cls-04-4,.cls-04-6{fill:#fff;}.cls-04-4{fill-opacity:0.4;}.cls-04-5{fill:#ec1c24;stroke-miterlimit:10;}.cls-04-6,.cls-04-7{fill-opacity:0.2;}""", + """""" + ), + Part( + """.cls-05-2{fill:#fff;fill-opacity:0.4;}.cls-05-2,.cls-05-3{stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-05-3{fill:none;stroke-width:0.75px;}""", + """""" + ), + Part( + """.cls-06-2,.cls-06-4{fill-opacity:0.2;}.cls-06-3,.cls-06-6{fill:none;}.cls-06-3,.cls-06-5,.cls-06-6{stroke:#000;}.cls-06-3,.cls-06-5{stroke-miterlimit:10;}.cls-06-4{fill:#fff;}.cls-06-5{fill:#ec1c24;stroke-width:0.75px;}.cls-06-6{stroke-linecap:round;stroke-linejoin:round;}""", + """""" + ), + Part( + """.cls-07-10,.cls-07-2,.cls-07-4,.cls-07-8,.cls-07-9{fill:none;}.cls-07-10,.cls-07-2,.cls-07-3,.cls-07-4,.cls-07-5,.cls-07-6,.cls-07-8,.cls-07-9{stroke:#000;}.cls-07-2,.cls-07-3,.cls-07-4,.cls-07-5,.cls-07-6,.cls-07-8{stroke-linecap:round;}.cls-07-2,.cls-07-3,.cls-07-4,.cls-07-5,.cls-07-6,.cls-07-8,.cls-07-9{stroke-linejoin:round;}.cls-07-3,.cls-07-5{fill:#fff;}.cls-07-3,.cls-07-5,.cls-07-6,.cls-07-7{fill-opacity:0.2;}.cls-07-3,.cls-07-4{stroke-width:0.75px;}.cls-07-10,.cls-07-8,.cls-07-9{stroke-width:1.5px;}.cls-07-10{stroke-miterlimit:10;}""", + """""" + ), + Part( + """.cls-08-2{fill:none;}.cls-08-2,.cls-08-3,.cls-08-5,.cls-08-6,.cls-08-7{stroke:#000;stroke-miterlimit:10;}.cls-08-3,.cls-08-4{fill:#fff;}.cls-08-3,.cls-08-4,.cls-08-8{fill-opacity:0.2;}.cls-08-5{fill:#716558;}.cls-08-6{fill:#9a8479;}.cls-08-7{fill:#c1b49a;}""", + """""" + ), + Part( + """.cls-09-2{fill:none;}.cls-09-2,.cls-09-4{stroke:#000;stroke-miterlimit:10;}.cls-09-3,.cls-09-4{fill:#fff;}.cls-09-3{fill-opacity:0.2;}.cls-09-5{fill:#d0d2d3;}.cls-09-6{fill-opacity:0.4;}""", + """""" + ) + ) + + private val bodies: List = listOf( + Part( + """.cls-10-2{fill:#fff;fill-opacity:0.4;}.cls-10-3,.cls-10-5,.cls-10-7{fill:none;}.cls-10-3,.cls-10-4,.cls-10-5,.cls-10-6,.cls-10-7,.cls-10-8{stroke:#000;stroke-miterlimit:10;}.cls-10-3{stroke-width:1.2px;}.cls-10-4,.cls-10-6,.cls-10-9{fill-opacity:0.2;}.cls-10-5{stroke-width:1.25px;}.cls-10-6{stroke-width:0.75px;}.cls-10-8{fill:#6d6e70;}""", + """""" + ), + Part( + """.cls-11-2{fill:#fff;}.cls-11-2,.cls-11-6{fill-opacity:0.2;}.cls-11-3{fill:none;}.cls-11-3,.cls-11-4{stroke:#000;stroke-miterlimit:10;}.cls-11-4{fill:#6d6e70;}.cls-11-5{opacity:0.2;}""", + """""" + ), + Part( + """.cls-12-2{fill:#fff;fill-opacity:0.4;}.cls-12-3,.cls-12-7{fill:none;}.cls-12-3,.cls-12-5,.cls-12-6,.cls-12-7{stroke:#000;}.cls-12-3,.cls-12-5,.cls-12-6{stroke-miterlimit:10;}.cls-12-4,.cls-12-6{fill-opacity:0.2;}.cls-12-5{fill:#6d6e70;}.cls-12-7{stroke-linecap:round;stroke-linejoin:round;}""", + """""" + ), + Part( + """.cls-13-2{fill:none;}.cls-13-2,.cls-13-5{stroke:#000;stroke-miterlimit:10;}.cls-13-3{fill:#fff;fill-opacity:0.4;}.cls-13-4{fill-opacity:0.2;}.cls-13-5{fill:#6d6e70;}""", + """""" + ), + Part( + """.cls-14-2{fill:none;}.cls-14-2,.cls-14-4{stroke:#000;stroke-miterlimit:10;}.cls-14-3,.cls-14-6{fill-opacity:0.2;}.cls-14-4{fill:#6d6e70;}.cls-14-5,.cls-14-6{fill:#fff;}.cls-14-5{fill-opacity:0.4;}""", + """""" + ), + Part( + """.cls-15-2{fill:#fff;}.cls-15-2,.cls-15-4{fill-opacity:0.2;}.cls-15-2,.cls-15-3,.cls-15-5{stroke:#000;stroke-miterlimit:10;}.cls-15-3{fill:none;}.cls-15-5{fill:#6d6e70;}""", + """""" + ), + Part( + """.cls-16-2,.cls-16-5{fill:none;}.cls-16-2,.cls-16-5,.cls-16-6,.cls-16-7,.cls-16-8,.cls-16-9{stroke:#000;}.cls-16-2{stroke-linecap:round;stroke-linejoin:round;}.cls-16-3,.cls-16-6,.cls-16-7{fill-opacity:0.2;}.cls-16-4,.cls-16-6{fill:#fff;}.cls-16-4{fill-opacity:0.4;}.cls-16-5,.cls-16-6,.cls-16-7,.cls-16-8,.cls-16-9{stroke-miterlimit:10;}.cls-16-8{fill:#f9ec31;}.cls-16-9{fill:#6d6e70;}""", + """""" + ), + Part( + """.cls-17-2{fill:#020202;}.cls-17-2,.cls-17-4,.cls-17-6,.cls-17-7{fill-opacity:0.4;}.cls-17-3{fill-opacity:0.2;}.cls-17-4,.cls-17-6{fill:#fff;}.cls-17-4,.cls-17-5,.cls-17-8{stroke:#000;stroke-miterlimit:10;}.cls-17-5{fill:none;}.cls-17-8{fill:#6d6e70;}""", + """""" + ), + Part( + """.cls-fill-2,.cls-18-3,.cls-18-5,.cls-18-6{stroke:#000;stroke-miterlimit:10;}.cls-18-3{fill:none;}.cls-18-4{fill:#fff;}.cls-18-4,.cls-18-5{fill-opacity:0.4;}.cls-18-6{fill:#6d6e70;}.cls-18-7{opacity:0.2;}.cls-18-8{fill-opacity:0.2;}""", + """""" + ), + Part( + """.cls-19-2{fill-opacity:0.6;}.cls-19-11,.cls-19-13,.cls-19-14,.cls-19-2,.cls-19-3,.cls-19-4,.cls-19-6,.cls-19-8{stroke:#000;}.cls-19-11,.cls-19-13,.cls-19-2,.cls-19-4,.cls-19-6,.cls-19-8{stroke-miterlimit:10;}.cls-19-11,.cls-19-14,.cls-19-3,.cls-19-8{fill:none;}.cls-19-14,.cls-19-3{stroke-linecap:round;stroke-linejoin:round;}.cls-19-10,.cls-19-12,.cls-19-4,.cls-19-5{fill:#fff;}.cls-19-12,.cls-19-4{fill-opacity:0.2;}.cls-19-4{stroke-opacity:0;}.cls-19-5{fill-opacity:0.1;}.cls-19-6{fill:#6d6e70;}.cls-19-7{fill:#58595b;}.cls-19-13,.cls-19-9{fill-opacity:0.4;}.cls-19-10{fill-opacity:0.5;}.cls-19-11,.cls-19-14{stroke-width:0.75px;}""", + """""" + ) + ) + + private val eyes: List = listOf( + Part( + """.cls-20-2{fill-opacity:0.4;}.cls-20-2,.cls-20-3{stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-20-3{fill:#461917;stroke-width:0.5px;}""", + """""" + ), + Part( + """.cls-21-2{fill-opacity:0.2;}.cls-21-2,.cls-21-3,.cls-21-4,.cls-21-5{stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-21-2,.cls-21-4,.cls-21-5{stroke-width:0.5px;}.cls-21-3,.cls-21-4{fill-opacity:0.4;}.cls-21-5{fill:#461917;}.cls-21-6{fill:#faaf40;}""", + """""" + ), + Part( + """.cls-22-2{opacity:0.4;}.cls-22-3{fill:#461917;}.cls-22-3,.cls-22-4,.cls-22-5{stroke:#000;}.cls-22-3,.cls-22-5{stroke-linecap:round;stroke-linejoin:round;}.cls-22-3,.cls-22-4{stroke-width:0.5px;}.cls-22-4{fill:#ec1c24;stroke-miterlimit:10;}.cls-22-5{fill:none;stroke-width:0.75px;}""", + """""" + ), + Part( + """.cls-23-2,.cls-23-3{fill:#fff;}.cls-23-2{fill-opacity:0.4;}.cls-23-3{fill-opacity:0.2;}.cls-23-3,.cls-23-4,.cls-23-5{stroke:#000;stroke-miterlimit:10;}.cls-23-4{fill:none;}.cls-23-4,.cls-23-5{stroke-width:0.75px;}.cls-23-5{fill:red;}""", + """""" + ), + Part( + """.cls-24-2{fill-opacity:0.4;stroke-miterlimit:10;}.cls-24-2,.cls-24-3,.cls-24-5{stroke:#000;}.cls-24-2,.cls-24-5{stroke-width:0.75px;}.cls-24-3,.cls-24-5{fill:#461917;stroke-linecap:round;stroke-linejoin:round;}.cls-24-3{stroke-width:0.5px;}.cls-24-4{fill:#ec1c24;}""", + """""" + ), + Part( + """.cls-25-2{fill-opacity:0.55;stroke-miterlimit:10;stroke-width:0.75px;}.cls-25-2,.cls-25-3{stroke:#000;}.cls-25-3{fill:#461917;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5px;}.cls-25-4{fill:#ec1c24;}""", + """""" + ), + Part( + """.cls-26-2{fill-opacity:0.6;}.cls-26-2,.cls-26-3,.cls-26-4,.cls-26-5,.cls-26-6{stroke:#000;}.cls-26-2,.cls-26-3,.cls-26-4,.cls-26-6{stroke-linecap:round;stroke-linejoin:round;}.cls-26-3{fill:#461917;}.cls-26-3,.cls-26-4,.cls-26-5{stroke-width:0.5px;}.cls-26-4,.cls-26-5{fill:#f9ec31;}.cls-26-5{stroke-miterlimit:10;}.cls-26-6{fill:none;}""", + """""" + ), + Part( + """.cls-27-2{fill-opacity:0.4;stroke-miterlimit:10;}.cls-27-2,.cls-27-3,.cls-27-4{stroke:#000;}.cls-27-3,.cls-27-4{fill:#461917;stroke-linecap:round;stroke-linejoin:round;}.cls-27-3{stroke-width:0.5px;}.cls-27-4{stroke-width:0.75px;}""", + """""" + ), + Part( + """.cls-28-2{fill:none;}.cls-28-2,.cls-28-3,.cls-28-4,.cls-28-6{stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-28-2,.cls-28-4,.cls-28-6{stroke-width:0.75px;}.cls-28-3{fill:#461917;stroke-width:0.5px;}.cls-28-4{fill-opacity:0.4;}.cls-28-5{fill:#fff100;}.cls-28-6{fill:#fff;fill-opacity:0.2;}""", + """""" + ), + Part( + """.cls-29-2{fill:#fff;}.cls-29-2,.cls-29-4{fill-opacity:0.4;}.cls-29-3{fill:none;}.cls-29-3,.cls-29-4{stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-29-4{stroke-width:0.75px;}""", + """""" + ) + ) + + private val faces: List = listOf( + Part( + """.cls-30-2{fill:#fff;fill-opacity:0.4;}.cls-30-3,.cls-30-4{fill:none;}.cls-30-3,.cls-30-4,.cls-30-6{stroke:#000;}.cls-30-3,.cls-30-6{stroke-linecap:round;stroke-linejoin:round;}.cls-30-4{stroke-miterlimit:10;}.cls-30-5,.cls-30-6{fill-opacity:0.2;}.cls-30-6{stroke-width:0.75px;}""", + """""" + ), + Part( + """.cls-31-2,.cls-31-4{fill-opacity:0.2;}.cls-31-3{fill:none;}.cls-31-3,.cls-31-4{stroke:#000;stroke-miterlimit:10;}.cls-31-4,.cls-31-5{fill:#fff;}.cls-31-4{stroke-width:0.75px;}.cls-31-5{fill-opacity:0.4;}""", + """""" + ), + Part( + """.cls-32-2{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-32-3{fill-opacity:0.2;}.cls-32-4{fill:#fff;fill-opacity:0.4;}""", + """""" + ), + Part( + """.cls-33-2{fill-opacity:0.2;}.cls-33-3{fill:#fff;fill-opacity:0.4;}.cls-33-4{fill:none;stroke:#000;stroke-miterlimit:10;}""", + """""" + ), + Part( + """.cls-34-2,.cls-34-3{fill:#fff;}.cls-34-2{fill-opacity:0.4;}.cls-34-3,.cls-34-5{fill-opacity:0.2;}.cls-34-3,.cls-34-4{stroke:#000;stroke-miterlimit:10;}.cls-34-4{fill:none;}""", + """""" + ), + Part( + """.cls-35-2{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-35-3{fill-opacity:0.2;}.cls-35-4{fill:#fff;fill-opacity:0.4;}""", + """""" + ), + Part( + """.cls-36-2,.cls-36-6{fill:#fff;}.cls-36-2{fill-opacity:0.4;}.cls-36-3,.cls-36-4{fill:none;stroke:#000;stroke-miterlimit:10;}.cls-36-3{stroke-width:2px;}.cls-36-5,.cls-36-6{fill-opacity:0.2;}""", + """""" + ), + Part( + """.cls-37-2{fill:#fff;fill-opacity:0.4;}.cls-37-3{fill:none;}.cls-37-3,.cls-37-4{stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-37-4{fill-opacity:0.2;}""", + """""" + ), + Part( + """.cls-38-2{fill:none;stroke:#000;stroke-miterlimit:10;}.cls-38-3,.cls-38-6{fill:#fff;}.cls-38-3,.cls-38-4{fill-opacity:0.2;}.cls-38-5{fill-opacity:0.1;}.cls-38-6{fill-opacity:0.4;}""", + """""" + ), + Part( + """.cls-39-2{fill:#fff;fill-opacity:0.4;}.cls-39-3{fill:none;}.cls-39-3,.cls-39-4{stroke:#000;stroke-miterlimit:10;}.cls-39-4{fill-opacity:0.2;stroke-width:0.75px;}""", + """""" + ) + ) + + private val mouths: List = listOf( + Part( + """.cls-40-2{fill-opacity:0.4;stroke:#000;stroke-linecap:round;stroke-linejoin:round;}""", + """""" + ), + Part( + """.cls-41-2,.cls-41-4{fill:none;}.cls-41-2,.cls-41-3,.cls-41-4{stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-41-3{fill-opacity:0.4;}.cls-41-3,.cls-41-4{stroke-width:0.75px;}""", + """""" + ), + Part( + """.cls-42-2{fill-opacity:0.4;}.cls-42-2,.cls-42-5,.cls-42-6{stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-42-3{fill:#ee4036;}.cls-42-4{fill:#f05a28;}.cls-42-5{fill:#faaf40;stroke-width:0.75px;}.cls-42-6{fill:none;}""", + """""" + ), + Part( + """.cls-43-2{fill:#f9ec31;}.cls-43-3{fill:#faaf40;}.cls-43-4,.cls-43-6{fill:none;}.cls-43-4,.cls-43-5,.cls-43-6{stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-43-5{fill-opacity:0.4;}.cls-43-5,.cls-43-6{stroke-width:0.75px;}""", + """""" + ), + Part( + """.cls-44-2{fill:none;}.cls-44-2,.cls-44-5{stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-44-3{opacity:0.4;}.cls-44-4{fill:#461917;}.cls-44-5{fill-opacity:0.4;stroke-width:0.75px;}""", + """""" + ), + Part( + """.cls-45-2{fill-opacity:0.4;stroke-miterlimit:10;}.cls-45-2,.cls-45-3{stroke:#000;}.cls-45-3{fill:#461917;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5px;}""", + """""" + ), + Part( + """.cls-46-2{fill-opacity:0.4;}.cls-46-3{fill:none;}.cls-46-3,.cls-46-4{stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-46-4{fill:#461917;stroke-width:0.5px;}""", + """""" + ), + Part( + """.cls-47-2{fill-opacity:0.4;}.cls-47-2,.cls-47-3,.cls-47-4,.cls-47-5{stroke:#000;stroke-miterlimit:10;}.cls-47-3{fill:#f6921e;}.cls-47-4{fill:#f9ec31;stroke-width:0.75px;}.cls-47-5{fill:none;}""", + """""" + ), + Part( + """.cls-48-2{opacity:0.4;}.cls-48-3,.cls-48-4,.cls-48-5{fill:none;}.cls-48-3,.cls-48-4,.cls-48-5,.cls-48-6,.cls-48-7,.cls-48-8{stroke:#000;}.cls-48-3,.cls-48-5,.cls-48-7{stroke-miterlimit:10;}.cls-48-4,.cls-48-6,.cls-48-8{stroke-linecap:round;stroke-linejoin:round;}.cls-48-4,.cls-48-5{stroke-width:0.5px;}.cls-48-6{fill:#f6921e;stroke-width:1.2px;}.cls-48-7{fill:#f9ec31;}.cls-48-7,.cls-48-8{stroke-width:0.75px;}.cls-48-8{fill:#f05a27;}""", + """""" + ), + Part( + """.cls-49-2{fill:none;}.cls-49-2,.cls-49-3,.cls-49-5{stroke:#000;}.cls-49-2,.cls-49-3{stroke-linecap:round;stroke-linejoin:round;}.cls-49-3{opacity:0.1;}.cls-49-4{opacity:0.4;}.cls-49-5{fill-opacity:0.4;stroke-miterlimit:10;stroke-width:0.75px;}""", + """""" + ) + ) +}