EmptyScreen.kt 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. package eu.kanade.presentation.components
  2. import androidx.annotation.StringRes
  3. import androidx.compose.foundation.layout.Arrangement
  4. import androidx.compose.foundation.layout.Column
  5. import androidx.compose.foundation.layout.Row
  6. import androidx.compose.foundation.layout.Spacer
  7. import androidx.compose.foundation.layout.fillMaxSize
  8. import androidx.compose.foundation.layout.height
  9. import androidx.compose.foundation.layout.padding
  10. import androidx.compose.foundation.layout.paddingFromBaseline
  11. import androidx.compose.material.icons.Icons
  12. import androidx.compose.material.icons.outlined.HelpOutline
  13. import androidx.compose.material.icons.outlined.Refresh
  14. import androidx.compose.material3.Icon
  15. import androidx.compose.material3.MaterialTheme
  16. import androidx.compose.material3.Surface
  17. import androidx.compose.material3.Text
  18. import androidx.compose.material3.TextButton
  19. import androidx.compose.runtime.Composable
  20. import androidx.compose.runtime.remember
  21. import androidx.compose.ui.Alignment
  22. import androidx.compose.ui.Modifier
  23. import androidx.compose.ui.graphics.vector.ImageVector
  24. import androidx.compose.ui.res.stringResource
  25. import androidx.compose.ui.text.style.TextAlign
  26. import androidx.compose.ui.unit.dp
  27. import eu.kanade.presentation.theme.TachiyomiTheme
  28. import eu.kanade.presentation.util.ThemePreviews
  29. import eu.kanade.presentation.util.secondaryItemAlpha
  30. import eu.kanade.tachiyomi.R
  31. import tachiyomi.presentation.core.components.material.padding
  32. import kotlin.random.Random
  33. @Composable
  34. fun EmptyScreen(
  35. @StringRes textResource: Int,
  36. modifier: Modifier = Modifier,
  37. actions: List<EmptyScreenAction>? = null,
  38. ) {
  39. EmptyScreen(
  40. message = stringResource(textResource),
  41. modifier = modifier,
  42. actions = actions,
  43. )
  44. }
  45. @Composable
  46. fun EmptyScreen(
  47. message: String,
  48. modifier: Modifier = Modifier,
  49. actions: List<EmptyScreenAction>? = null,
  50. ) {
  51. val face = remember { getRandomErrorFace() }
  52. Column(
  53. modifier = modifier
  54. .fillMaxSize()
  55. .padding(horizontal = 24.dp),
  56. horizontalAlignment = Alignment.CenterHorizontally,
  57. verticalArrangement = Arrangement.Center,
  58. ) {
  59. Text(
  60. text = face,
  61. modifier = Modifier.secondaryItemAlpha(),
  62. style = MaterialTheme.typography.displayMedium,
  63. )
  64. Text(
  65. text = message,
  66. modifier = Modifier.paddingFromBaseline(top = 24.dp).secondaryItemAlpha(),
  67. style = MaterialTheme.typography.bodyMedium,
  68. textAlign = TextAlign.Center,
  69. )
  70. if (!actions.isNullOrEmpty()) {
  71. Row(
  72. modifier = Modifier
  73. .padding(
  74. top = 24.dp,
  75. start = 24.dp,
  76. end = 24.dp,
  77. ),
  78. horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
  79. ) {
  80. actions.forEach {
  81. ActionButton(
  82. modifier = Modifier.weight(1f),
  83. title = stringResource(it.stringResId),
  84. icon = it.icon,
  85. onClick = it.onClick,
  86. )
  87. }
  88. }
  89. }
  90. }
  91. }
  92. @Composable
  93. private fun ActionButton(
  94. modifier: Modifier = Modifier,
  95. title: String,
  96. icon: ImageVector,
  97. onClick: () -> Unit,
  98. ) {
  99. TextButton(
  100. modifier = modifier,
  101. onClick = onClick,
  102. ) {
  103. Column(horizontalAlignment = Alignment.CenterHorizontally) {
  104. Icon(
  105. imageVector = icon,
  106. contentDescription = null,
  107. )
  108. Spacer(Modifier.height(4.dp))
  109. Text(
  110. text = title,
  111. textAlign = TextAlign.Center,
  112. )
  113. }
  114. }
  115. }
  116. @ThemePreviews
  117. @Composable
  118. private fun NoActionPreview() {
  119. TachiyomiTheme {
  120. Surface {
  121. EmptyScreen(
  122. textResource = R.string.empty_screen,
  123. )
  124. }
  125. }
  126. }
  127. @ThemePreviews
  128. @Composable
  129. private fun WithActionPreview() {
  130. TachiyomiTheme {
  131. Surface {
  132. EmptyScreen(
  133. textResource = R.string.empty_screen,
  134. actions = listOf(
  135. EmptyScreenAction(
  136. stringResId = R.string.action_retry,
  137. icon = Icons.Outlined.Refresh,
  138. onClick = {},
  139. ),
  140. EmptyScreenAction(
  141. stringResId = R.string.getting_started_guide,
  142. icon = Icons.Outlined.HelpOutline,
  143. onClick = {},
  144. ),
  145. ),
  146. )
  147. }
  148. }
  149. }
  150. data class EmptyScreenAction(
  151. @StringRes val stringResId: Int,
  152. val icon: ImageVector,
  153. val onClick: () -> Unit,
  154. )
  155. private val ERROR_FACES = listOf(
  156. "(・o・;)",
  157. "Σ(ಠ_ಠ)",
  158. "ಥ_ಥ",
  159. "(˘・_・˘)",
  160. "(; ̄Д ̄)",
  161. "(・Д・。",
  162. )
  163. private fun getRandomErrorFace(): String {
  164. return ERROR_FACES[Random.nextInt(ERROR_FACES.size)]
  165. }