ExtensionDetailsScreen.kt 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. package eu.kanade.presentation.browse
  2. import androidx.annotation.StringRes
  3. import androidx.compose.foundation.background
  4. import androidx.compose.foundation.layout.Box
  5. import androidx.compose.foundation.layout.Column
  6. import androidx.compose.foundation.layout.Row
  7. import androidx.compose.foundation.layout.Spacer
  8. import androidx.compose.foundation.layout.WindowInsets
  9. import androidx.compose.foundation.layout.asPaddingValues
  10. import androidx.compose.foundation.layout.fillMaxWidth
  11. import androidx.compose.foundation.layout.height
  12. import androidx.compose.foundation.layout.navigationBars
  13. import androidx.compose.foundation.layout.padding
  14. import androidx.compose.foundation.layout.width
  15. import androidx.compose.foundation.lazy.LazyColumn
  16. import androidx.compose.foundation.lazy.items
  17. import androidx.compose.material.icons.Icons
  18. import androidx.compose.material.icons.outlined.Settings
  19. import androidx.compose.material3.Button
  20. import androidx.compose.material3.Icon
  21. import androidx.compose.material3.IconButton
  22. import androidx.compose.material3.MaterialTheme
  23. import androidx.compose.material3.OutlinedButton
  24. import androidx.compose.material3.Switch
  25. import androidx.compose.material3.Text
  26. import androidx.compose.runtime.Composable
  27. import androidx.compose.runtime.collectAsState
  28. import androidx.compose.runtime.getValue
  29. import androidx.compose.ui.Alignment
  30. import androidx.compose.ui.Modifier
  31. import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
  32. import androidx.compose.ui.input.nestedscroll.nestedScroll
  33. import androidx.compose.ui.platform.LocalContext
  34. import androidx.compose.ui.res.stringResource
  35. import androidx.compose.ui.unit.dp
  36. import eu.kanade.presentation.browse.components.ExtensionIcon
  37. import eu.kanade.presentation.components.Divider
  38. import eu.kanade.presentation.components.EmptyScreen
  39. import eu.kanade.presentation.components.PreferenceRow
  40. import eu.kanade.presentation.util.horizontalPadding
  41. import eu.kanade.tachiyomi.R
  42. import eu.kanade.tachiyomi.extension.model.Extension
  43. import eu.kanade.tachiyomi.source.ConfigurableSource
  44. import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsPresenter
  45. import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionSourceItem
  46. import eu.kanade.tachiyomi.util.system.LocaleHelper
  47. @Composable
  48. fun ExtensionDetailsScreen(
  49. nestedScrollInterop: NestedScrollConnection,
  50. presenter: ExtensionDetailsPresenter,
  51. onClickUninstall: () -> Unit,
  52. onClickAppInfo: () -> Unit,
  53. onClickSourcePreferences: (sourceId: Long) -> Unit,
  54. onClickSource: (sourceId: Long) -> Unit,
  55. ) {
  56. val extension = presenter.extension
  57. if (extension == null) {
  58. EmptyScreen(textResource = R.string.empty_screen)
  59. return
  60. }
  61. val sources by presenter.sourcesState.collectAsState()
  62. LazyColumn(
  63. modifier = Modifier.nestedScroll(nestedScrollInterop),
  64. contentPadding = WindowInsets.navigationBars.asPaddingValues(),
  65. ) {
  66. when {
  67. extension.isUnofficial ->
  68. item {
  69. WarningBanner(R.string.unofficial_extension_message)
  70. }
  71. extension.isObsolete ->
  72. item {
  73. WarningBanner(R.string.obsolete_extension_message)
  74. }
  75. }
  76. item {
  77. DetailsHeader(extension, onClickUninstall, onClickAppInfo)
  78. }
  79. items(
  80. items = sources,
  81. key = { it.source.id },
  82. ) { source ->
  83. SourceSwitchPreference(
  84. modifier = Modifier.animateItemPlacement(),
  85. source = source,
  86. onClickSourcePreferences = onClickSourcePreferences,
  87. onClickSource = onClickSource,
  88. )
  89. }
  90. }
  91. }
  92. @Composable
  93. private fun WarningBanner(@StringRes textRes: Int) {
  94. Box(
  95. modifier = Modifier
  96. .fillMaxWidth()
  97. .background(MaterialTheme.colorScheme.error)
  98. .padding(16.dp),
  99. contentAlignment = Alignment.Center,
  100. ) {
  101. Text(
  102. text = stringResource(textRes),
  103. color = MaterialTheme.colorScheme.onError,
  104. )
  105. }
  106. }
  107. @Composable
  108. private fun DetailsHeader(
  109. extension: Extension,
  110. onClickUninstall: () -> Unit,
  111. onClickAppInfo: () -> Unit,
  112. ) {
  113. val context = LocalContext.current
  114. Column {
  115. Row(
  116. modifier = Modifier.padding(
  117. start = horizontalPadding,
  118. end = horizontalPadding,
  119. top = 16.dp,
  120. bottom = 8.dp,
  121. ),
  122. ) {
  123. ExtensionIcon(
  124. modifier = Modifier
  125. .height(56.dp)
  126. .width(56.dp),
  127. extension = extension,
  128. )
  129. Column(
  130. modifier = Modifier.padding(start = 16.dp),
  131. ) {
  132. Text(
  133. text = extension.name,
  134. style = MaterialTheme.typography.titleMedium,
  135. )
  136. Text(
  137. text = stringResource(R.string.ext_version_info, extension.versionName),
  138. style = MaterialTheme.typography.bodySmall,
  139. )
  140. Text(
  141. text = stringResource(R.string.ext_language_info, LocaleHelper.getSourceDisplayName(extension.lang, context)),
  142. style = MaterialTheme.typography.bodySmall,
  143. )
  144. if (extension.isNsfw) {
  145. Text(
  146. text = stringResource(R.string.ext_nsfw_warning),
  147. color = MaterialTheme.colorScheme.error,
  148. style = MaterialTheme.typography.bodySmall,
  149. )
  150. }
  151. Text(
  152. text = extension.pkgName,
  153. style = MaterialTheme.typography.bodySmall,
  154. )
  155. }
  156. }
  157. Row(
  158. modifier = Modifier.padding(
  159. start = horizontalPadding,
  160. end = horizontalPadding,
  161. top = 8.dp,
  162. bottom = 16.dp,
  163. ),
  164. ) {
  165. OutlinedButton(
  166. modifier = Modifier.weight(1f),
  167. onClick = onClickUninstall,
  168. ) {
  169. Text(stringResource(R.string.ext_uninstall))
  170. }
  171. Spacer(Modifier.width(16.dp))
  172. Button(
  173. modifier = Modifier.weight(1f),
  174. onClick = onClickAppInfo,
  175. ) {
  176. Text(
  177. text = stringResource(R.string.ext_app_info),
  178. color = MaterialTheme.colorScheme.onPrimary,
  179. )
  180. }
  181. }
  182. Divider()
  183. }
  184. }
  185. @Composable
  186. private fun SourceSwitchPreference(
  187. modifier: Modifier = Modifier,
  188. source: ExtensionSourceItem,
  189. onClickSourcePreferences: (sourceId: Long) -> Unit,
  190. onClickSource: (sourceId: Long) -> Unit,
  191. ) {
  192. val context = LocalContext.current
  193. PreferenceRow(
  194. modifier = modifier,
  195. title = if (source.labelAsName) {
  196. source.source.toString()
  197. } else {
  198. LocaleHelper.getSourceDisplayName(source.source.lang, context)
  199. },
  200. onClick = { onClickSource(source.source.id) },
  201. action = {
  202. Row(
  203. verticalAlignment = Alignment.CenterVertically,
  204. ) {
  205. if (source.source is ConfigurableSource) {
  206. IconButton(onClick = { onClickSourcePreferences(source.source.id) }) {
  207. Icon(
  208. imageVector = Icons.Outlined.Settings,
  209. contentDescription = stringResource(R.string.label_settings),
  210. tint = MaterialTheme.colorScheme.onSurface,
  211. )
  212. }
  213. }
  214. Switch(checked = source.enabled, onCheckedChange = null)
  215. }
  216. },
  217. )
  218. }