MangaCoverDialog.kt 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. package eu.kanade.presentation.manga.components
  2. import android.graphics.Bitmap
  3. import android.graphics.drawable.BitmapDrawable
  4. import android.os.Build
  5. import androidx.compose.foundation.background
  6. import androidx.compose.foundation.layout.Box
  7. import androidx.compose.foundation.layout.Row
  8. import androidx.compose.foundation.layout.Spacer
  9. import androidx.compose.foundation.layout.fillMaxSize
  10. import androidx.compose.foundation.layout.fillMaxWidth
  11. import androidx.compose.foundation.layout.navigationBarsPadding
  12. import androidx.compose.foundation.layout.padding
  13. import androidx.compose.material.icons.Icons
  14. import androidx.compose.material.icons.outlined.Close
  15. import androidx.compose.material.icons.outlined.Edit
  16. import androidx.compose.material.icons.outlined.Save
  17. import androidx.compose.material.icons.outlined.Share
  18. import androidx.compose.material3.DropdownMenuItem
  19. import androidx.compose.material3.Icon
  20. import androidx.compose.material3.IconButton
  21. import androidx.compose.material3.MaterialTheme
  22. import androidx.compose.material3.SnackbarHost
  23. import androidx.compose.material3.SnackbarHostState
  24. import androidx.compose.material3.Text
  25. import androidx.compose.runtime.Composable
  26. import androidx.compose.runtime.getValue
  27. import androidx.compose.runtime.mutableStateOf
  28. import androidx.compose.runtime.remember
  29. import androidx.compose.runtime.setValue
  30. import androidx.compose.ui.Modifier
  31. import androidx.compose.ui.draw.clip
  32. import androidx.compose.ui.graphics.Color
  33. import androidx.compose.ui.platform.LocalDensity
  34. import androidx.compose.ui.unit.DpOffset
  35. import androidx.compose.ui.unit.dp
  36. import androidx.compose.ui.viewinterop.AndroidView
  37. import androidx.compose.ui.window.Dialog
  38. import androidx.compose.ui.window.DialogProperties
  39. import androidx.core.view.updatePadding
  40. import coil.imageLoader
  41. import coil.request.CachePolicy
  42. import coil.request.ImageRequest
  43. import coil.size.Size
  44. import eu.kanade.presentation.components.AppBar
  45. import eu.kanade.presentation.components.AppBarActions
  46. import eu.kanade.presentation.components.DropdownMenu
  47. import eu.kanade.presentation.manga.EditCoverAction
  48. import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
  49. import kotlinx.collections.immutable.persistentListOf
  50. import tachiyomi.domain.manga.model.Manga
  51. import tachiyomi.i18n.MR
  52. import tachiyomi.presentation.core.components.material.Scaffold
  53. import tachiyomi.presentation.core.i18n.localize
  54. import tachiyomi.presentation.core.util.clickableNoIndication
  55. @Composable
  56. fun MangaCoverDialog(
  57. coverDataProvider: () -> Manga,
  58. isCustomCover: Boolean,
  59. snackbarHostState: SnackbarHostState,
  60. onShareClick: () -> Unit,
  61. onSaveClick: () -> Unit,
  62. onEditClick: ((EditCoverAction) -> Unit)?,
  63. onDismissRequest: () -> Unit,
  64. ) {
  65. Dialog(
  66. onDismissRequest = onDismissRequest,
  67. properties = DialogProperties(
  68. usePlatformDefaultWidth = false,
  69. decorFitsSystemWindows = false, // Doesn't work https://issuetracker.google.com/issues/246909281
  70. ),
  71. ) {
  72. Scaffold(
  73. snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
  74. containerColor = Color.Transparent,
  75. bottomBar = {
  76. Row(
  77. modifier = Modifier
  78. .fillMaxWidth()
  79. .padding(4.dp)
  80. .navigationBarsPadding(),
  81. ) {
  82. ActionsPill {
  83. IconButton(onClick = onDismissRequest) {
  84. Icon(
  85. imageVector = Icons.Outlined.Close,
  86. contentDescription = localize(MR.strings.action_close),
  87. )
  88. }
  89. }
  90. Spacer(modifier = Modifier.weight(1f))
  91. ActionsPill {
  92. AppBarActions(
  93. actions = persistentListOf(
  94. AppBar.Action(
  95. title = localize(MR.strings.action_share),
  96. icon = Icons.Outlined.Share,
  97. onClick = onShareClick,
  98. ),
  99. AppBar.Action(
  100. title = localize(MR.strings.action_save),
  101. icon = Icons.Outlined.Save,
  102. onClick = onSaveClick,
  103. ),
  104. ),
  105. )
  106. if (onEditClick != null) {
  107. Box {
  108. var expanded by remember { mutableStateOf(false) }
  109. IconButton(
  110. onClick = {
  111. if (isCustomCover) {
  112. expanded = true
  113. } else {
  114. onEditClick(EditCoverAction.EDIT)
  115. }
  116. },
  117. ) {
  118. Icon(
  119. imageVector = Icons.Outlined.Edit,
  120. contentDescription = localize(MR.strings.action_edit_cover),
  121. )
  122. }
  123. DropdownMenu(
  124. expanded = expanded,
  125. onDismissRequest = { expanded = false },
  126. offset = DpOffset(8.dp, 0.dp),
  127. ) {
  128. DropdownMenuItem(
  129. text = { Text(text = localize(MR.strings.action_edit)) },
  130. onClick = {
  131. onEditClick(EditCoverAction.EDIT)
  132. expanded = false
  133. },
  134. )
  135. DropdownMenuItem(
  136. text = { Text(text = localize(MR.strings.action_delete)) },
  137. onClick = {
  138. onEditClick(EditCoverAction.DELETE)
  139. expanded = false
  140. },
  141. )
  142. }
  143. }
  144. }
  145. }
  146. }
  147. },
  148. ) { contentPadding ->
  149. val statusBarPaddingPx = with(LocalDensity.current) { contentPadding.calculateTopPadding().roundToPx() }
  150. val bottomPaddingPx = with(LocalDensity.current) { contentPadding.calculateBottomPadding().roundToPx() }
  151. Box(
  152. modifier = Modifier
  153. .fillMaxSize()
  154. .clickableNoIndication(onClick = onDismissRequest),
  155. ) {
  156. AndroidView(
  157. factory = {
  158. ReaderPageImageView(it).apply {
  159. onViewClicked = onDismissRequest
  160. clipToPadding = false
  161. clipChildren = false
  162. }
  163. },
  164. update = { view ->
  165. val request = ImageRequest.Builder(view.context)
  166. .data(coverDataProvider())
  167. .size(Size.ORIGINAL)
  168. .memoryCachePolicy(CachePolicy.DISABLED)
  169. .target { drawable ->
  170. // Copy bitmap in case it came from memory cache
  171. // Because SSIV needs to thoroughly read the image
  172. val copy = (drawable as? BitmapDrawable)?.let {
  173. val config = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  174. Bitmap.Config.HARDWARE
  175. } else {
  176. Bitmap.Config.ARGB_8888
  177. }
  178. BitmapDrawable(
  179. view.context.resources,
  180. it.bitmap.copy(config, false),
  181. )
  182. } ?: drawable
  183. view.setImage(copy, ReaderPageImageView.Config(zoomDuration = 500))
  184. }
  185. .build()
  186. view.context.imageLoader.enqueue(request)
  187. view.updatePadding(top = statusBarPaddingPx, bottom = bottomPaddingPx)
  188. },
  189. modifier = Modifier.fillMaxSize(),
  190. )
  191. }
  192. }
  193. }
  194. }
  195. @Composable
  196. private fun ActionsPill(content: @Composable () -> Unit) {
  197. Row(
  198. modifier = Modifier
  199. .clip(MaterialTheme.shapes.extraLarge)
  200. .background(MaterialTheme.colorScheme.background.copy(alpha = 0.95f)),
  201. ) {
  202. content()
  203. }
  204. }