package eu.kanade.presentation.browse.components import android.content.pm.PackageManager import android.util.DisplayMetrics import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.height import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Dangerous import androidx.compose.material.icons.filled.Warning import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.getValue import androidx.compose.runtime.produceState import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.painter.ColorPainter import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.imageResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import androidx.core.graphics.drawable.toBitmap import coil.compose.AsyncImage import eu.kanade.domain.source.model.icon import eu.kanade.presentation.util.rememberResourceBitmapPainter import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.extension.model.Extension import tachiyomi.core.util.lang.withIOContext import tachiyomi.domain.source.model.Source import tachiyomi.source.local.isLocal private val defaultModifier = Modifier .height(40.dp) .aspectRatio(1f) @Composable fun SourceIcon( source: Source, modifier: Modifier = Modifier, ) { val icon = source.icon when { source.isStub && icon == null -> { Image( imageVector = Icons.Filled.Warning, contentDescription = null, colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.error), modifier = modifier.then(defaultModifier), ) } icon != null -> { Image( bitmap = icon, contentDescription = null, modifier = modifier.then(defaultModifier), ) } source.isLocal() -> { Image( painter = painterResource(R.mipmap.ic_local_source), contentDescription = null, modifier = modifier.then(defaultModifier), ) } else -> { Image( painter = painterResource(R.mipmap.ic_default_source), contentDescription = null, modifier = modifier.then(defaultModifier), ) } } } @Composable fun ExtensionIcon( extension: Extension, modifier: Modifier = Modifier, density: Int = DisplayMetrics.DENSITY_DEFAULT, ) { when (extension) { is Extension.Available -> { AsyncImage( model = extension.iconUrl, contentDescription = null, placeholder = ColorPainter(Color(0x1F888888)), error = rememberResourceBitmapPainter(id = R.drawable.cover_error), modifier = modifier .clip(MaterialTheme.shapes.extraSmall), ) } is Extension.Installed -> { val icon by extension.getIcon(density) when (icon) { Result.Loading -> Box(modifier = modifier) is Result.Success -> Image( bitmap = (icon as Result.Success).value, contentDescription = null, modifier = modifier, ) Result.Error -> Image( bitmap = ImageBitmap.imageResource(id = R.mipmap.ic_default_source), contentDescription = null, modifier = modifier, ) } } is Extension.Untrusted -> Image( imageVector = Icons.Filled.Dangerous, contentDescription = null, colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.error), modifier = modifier.then(defaultModifier), ) } } @Composable private fun Extension.getIcon(density: Int = DisplayMetrics.DENSITY_DEFAULT): State> { val context = LocalContext.current return produceState>(initialValue = Result.Loading, this) { withIOContext { value = try { val appInfo = context.packageManager.getApplicationInfo(pkgName, PackageManager.GET_META_DATA) val appResources = context.packageManager.getResourcesForApplication(appInfo) Result.Success( appResources.getDrawableForDensity(appInfo.icon, density, null)!! .toBitmap() .asImageBitmap(), ) } catch (e: Exception) { Result.Error } } } } sealed class Result { data object Loading : Result() data object Error : Result() data class Success(val value: T) : Result() }