BrowseIcons.kt 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. package eu.kanade.presentation.browse.components
  2. import android.content.pm.PackageManager
  3. import android.util.DisplayMetrics
  4. import androidx.compose.foundation.Image
  5. import androidx.compose.foundation.layout.Box
  6. import androidx.compose.foundation.layout.aspectRatio
  7. import androidx.compose.foundation.layout.height
  8. import androidx.compose.material.icons.Icons
  9. import androidx.compose.material.icons.filled.Dangerous
  10. import androidx.compose.material.icons.filled.Warning
  11. import androidx.compose.material3.MaterialTheme
  12. import androidx.compose.runtime.Composable
  13. import androidx.compose.runtime.State
  14. import androidx.compose.runtime.getValue
  15. import androidx.compose.runtime.produceState
  16. import androidx.compose.ui.Modifier
  17. import androidx.compose.ui.draw.clip
  18. import androidx.compose.ui.graphics.Color
  19. import androidx.compose.ui.graphics.ColorFilter
  20. import androidx.compose.ui.graphics.ImageBitmap
  21. import androidx.compose.ui.graphics.asImageBitmap
  22. import androidx.compose.ui.graphics.painter.ColorPainter
  23. import androidx.compose.ui.platform.LocalContext
  24. import androidx.compose.ui.res.imageResource
  25. import androidx.compose.ui.res.painterResource
  26. import androidx.compose.ui.unit.dp
  27. import androidx.core.graphics.drawable.toBitmap
  28. import coil.compose.AsyncImage
  29. import eu.kanade.domain.source.model.Source
  30. import eu.kanade.presentation.util.rememberResourceBitmapPainter
  31. import eu.kanade.tachiyomi.R
  32. import eu.kanade.tachiyomi.extension.model.Extension
  33. import eu.kanade.tachiyomi.util.lang.withIOContext
  34. private val defaultModifier = Modifier
  35. .height(40.dp)
  36. .aspectRatio(1f)
  37. @Composable
  38. fun SourceIcon(
  39. source: Source,
  40. modifier: Modifier = Modifier,
  41. ) {
  42. val icon = source.icon
  43. when {
  44. source.isStub && icon == null -> {
  45. Image(
  46. imageVector = Icons.Default.Warning,
  47. contentDescription = null,
  48. colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.error),
  49. modifier = modifier.then(defaultModifier),
  50. )
  51. }
  52. icon != null -> {
  53. Image(
  54. bitmap = icon,
  55. contentDescription = null,
  56. modifier = modifier.then(defaultModifier),
  57. )
  58. }
  59. else -> {
  60. Image(
  61. painter = painterResource(id = R.mipmap.ic_local_source),
  62. contentDescription = null,
  63. modifier = modifier.then(defaultModifier),
  64. )
  65. }
  66. }
  67. }
  68. @Composable
  69. fun ExtensionIcon(
  70. extension: Extension,
  71. modifier: Modifier = Modifier,
  72. density: Int = DisplayMetrics.DENSITY_DEFAULT,
  73. ) {
  74. when (extension) {
  75. is Extension.Available -> {
  76. AsyncImage(
  77. model = extension.iconUrl,
  78. contentDescription = null,
  79. placeholder = ColorPainter(Color(0x1F888888)),
  80. error = rememberResourceBitmapPainter(id = R.drawable.cover_error),
  81. modifier = modifier
  82. .clip(MaterialTheme.shapes.extraSmall),
  83. )
  84. }
  85. is Extension.Installed -> {
  86. val icon by extension.getIcon(density)
  87. when (icon) {
  88. Result.Error -> Image(
  89. bitmap = ImageBitmap.imageResource(id = R.mipmap.ic_local_source),
  90. contentDescription = null,
  91. modifier = modifier,
  92. )
  93. Result.Loading -> Box(modifier = modifier)
  94. is Result.Success -> Image(
  95. bitmap = (icon as Result.Success<ImageBitmap>).value,
  96. contentDescription = null,
  97. modifier = modifier,
  98. )
  99. }
  100. }
  101. is Extension.Untrusted -> Image(
  102. imageVector = Icons.Default.Dangerous,
  103. contentDescription = null,
  104. colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.error),
  105. modifier = modifier.then(defaultModifier),
  106. )
  107. }
  108. }
  109. @Composable
  110. private fun Extension.getIcon(density: Int = DisplayMetrics.DENSITY_DEFAULT): State<Result<ImageBitmap>> {
  111. val context = LocalContext.current
  112. return produceState<Result<ImageBitmap>>(initialValue = Result.Loading, this) {
  113. withIOContext {
  114. value = try {
  115. val appInfo = context.packageManager.getApplicationInfo(pkgName, PackageManager.GET_META_DATA)
  116. val appResources = context.packageManager.getResourcesForApplication(appInfo)
  117. Result.Success(
  118. appResources.getDrawableForDensity(appInfo.icon, density, null)!!
  119. .toBitmap()
  120. .asImageBitmap(),
  121. )
  122. } catch (e: Exception) {
  123. Result.Error
  124. }
  125. }
  126. }
  127. }
  128. sealed class Result<out T> {
  129. object Loading : Result<Nothing>()
  130. object Error : Result<Nothing>()
  131. data class Success<out T>(val value: T) : Result<T>()
  132. }