BrowseIcons.kt 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  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.icon
  30. import eu.kanade.presentation.util.rememberResourceBitmapPainter
  31. import eu.kanade.tachiyomi.R
  32. import eu.kanade.tachiyomi.extension.model.Extension
  33. import tachiyomi.core.util.lang.withIOContext
  34. import tachiyomi.domain.source.model.Source
  35. import tachiyomi.source.local.isLocal
  36. private val defaultModifier = Modifier
  37. .height(40.dp)
  38. .aspectRatio(1f)
  39. @Composable
  40. fun SourceIcon(
  41. source: Source,
  42. modifier: Modifier = Modifier,
  43. ) {
  44. val icon = source.icon
  45. when {
  46. source.isStub && icon == null -> {
  47. Image(
  48. imageVector = Icons.Filled.Warning,
  49. contentDescription = null,
  50. colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.error),
  51. modifier = modifier.then(defaultModifier),
  52. )
  53. }
  54. icon != null -> {
  55. Image(
  56. bitmap = icon,
  57. contentDescription = null,
  58. modifier = modifier.then(defaultModifier),
  59. )
  60. }
  61. source.isLocal() -> {
  62. Image(
  63. painter = painterResource(R.mipmap.ic_local_source),
  64. contentDescription = null,
  65. modifier = modifier.then(defaultModifier),
  66. )
  67. }
  68. else -> {
  69. Image(
  70. painter = painterResource(R.mipmap.ic_default_source),
  71. contentDescription = null,
  72. modifier = modifier.then(defaultModifier),
  73. )
  74. }
  75. }
  76. }
  77. @Composable
  78. fun ExtensionIcon(
  79. extension: Extension,
  80. modifier: Modifier = Modifier,
  81. density: Int = DisplayMetrics.DENSITY_DEFAULT,
  82. ) {
  83. when (extension) {
  84. is Extension.Available -> {
  85. AsyncImage(
  86. model = extension.iconUrl,
  87. contentDescription = null,
  88. placeholder = ColorPainter(Color(0x1F888888)),
  89. error = rememberResourceBitmapPainter(id = R.drawable.cover_error),
  90. modifier = modifier
  91. .clip(MaterialTheme.shapes.extraSmall),
  92. )
  93. }
  94. is Extension.Installed -> {
  95. val icon by extension.getIcon(density)
  96. when (icon) {
  97. Result.Loading -> Box(modifier = modifier)
  98. is Result.Success -> Image(
  99. bitmap = (icon as Result.Success<ImageBitmap>).value,
  100. contentDescription = null,
  101. modifier = modifier,
  102. )
  103. Result.Error -> Image(
  104. bitmap = ImageBitmap.imageResource(id = R.mipmap.ic_default_source),
  105. contentDescription = null,
  106. modifier = modifier,
  107. )
  108. }
  109. }
  110. is Extension.Untrusted -> Image(
  111. imageVector = Icons.Filled.Dangerous,
  112. contentDescription = null,
  113. colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.error),
  114. modifier = modifier.then(defaultModifier),
  115. )
  116. }
  117. }
  118. @Composable
  119. private fun Extension.getIcon(density: Int = DisplayMetrics.DENSITY_DEFAULT): State<Result<ImageBitmap>> {
  120. val context = LocalContext.current
  121. return produceState<Result<ImageBitmap>>(initialValue = Result.Loading, this) {
  122. withIOContext {
  123. value = try {
  124. val appInfo = context.packageManager.getApplicationInfo(pkgName, PackageManager.GET_META_DATA)
  125. val appResources = context.packageManager.getResourcesForApplication(appInfo)
  126. Result.Success(
  127. appResources.getDrawableForDensity(appInfo.icon, density, null)!!
  128. .toBitmap()
  129. .asImageBitmap(),
  130. )
  131. } catch (e: Exception) {
  132. Result.Error
  133. }
  134. }
  135. }
  136. }
  137. sealed class Result<out T> {
  138. data object Loading : Result<Nothing>()
  139. data object Error : Result<Nothing>()
  140. data class Success<out T>(val value: T) : Result<T>()
  141. }