From f2b0d74b4cd6740b708e587f18c6cc798287dbe8 Mon Sep 17 00:00:00 2001 From: arkon Date: Fri, 23 Jun 2023 23:17:47 -0400 Subject: [PATCH] Migrate ReaderPageSheet to Compose --- .../tachiyomi/ui/reader/ReaderActivity.kt | 40 +++---- .../tachiyomi/ui/reader/ReaderPageDialog.kt | 102 ++++++++++++++++++ .../tachiyomi/ui/reader/ReaderPageSheet.kt | 62 ----------- .../tachiyomi/ui/reader/ReaderViewModel.kt | 34 ++++-- app/src/main/res/drawable/ic_save_24dp.xml | 9 -- app/src/main/res/layout/reader_activity.xml | 5 + app/src/main/res/layout/reader_page_sheet.xml | 53 --------- .../core/components/ActionButton.kt | 40 +++++++ .../presentation/core/screens/EmptyScreen.kt | 30 +----- 9 files changed, 188 insertions(+), 187 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageDialog.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt delete mode 100644 app/src/main/res/drawable/ic_save_24dp.xml delete mode 100644 app/src/main/res/layout/reader_page_sheet.xml create mode 100644 presentation-core/src/main/java/tachiyomi/presentation/core/components/ActionButton.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index 1e275b99e6..3e9fc10d75 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -407,6 +407,20 @@ class ReaderActivity : BaseActivity() { ) } + binding.dialogRoot.setComposeContent { + val state by viewModel.state.collectAsState() + + when (state.dialog) { + is ReaderViewModel.Dialog.Page -> ReaderPageDialog( + onDismissRequest = viewModel::closeDialog, + onSetAsCover = viewModel::setAsCover, + onShare = viewModel::shareImage, + onSave = viewModel::saveImage, + ) + null -> {} + } + } + // Init listeners on bottom menu binding.readerNav.setComposeContent { val state by viewModel.state.collectAsState() @@ -786,7 +800,7 @@ class ReaderActivity : BaseActivity() { * actions to perform is shown. */ fun onPageLongTap(page: ReaderPage) { - ReaderPageSheet(this, page).show() + viewModel.openPageDialog(page) } /** @@ -823,14 +837,6 @@ class ReaderActivity : BaseActivity() { } } - /** - * Called from the page sheet. It delegates the call to the presenter to do some IO, which - * will call [onShareImageResult] with the path the image was saved on when it's ready. - */ - fun shareImage(page: ReaderPage) { - viewModel.shareImage(page) - } - /** * Called from the presenter when a page is ready to be shared. It shows Android's default * sharing tool. @@ -846,14 +852,6 @@ class ReaderActivity : BaseActivity() { startActivity(Intent.createChooser(intent, getString(R.string.action_share))) } - /** - * Called from the page sheet. It delegates saving the image of the given [page] on external - * storage to the presenter. - */ - fun saveImage(page: ReaderPage) { - viewModel.saveImage(page) - } - /** * Called from the presenter when a page is saved or fails. It shows a message or logs the * event depending on the [result]. @@ -869,14 +867,6 @@ class ReaderActivity : BaseActivity() { } } - /** - * Called from the page sheet. It delegates setting the image of the given [page] as the - * cover to the presenter. - */ - fun setAsCover(page: ReaderPage) { - viewModel.setAsCover(page) - } - /** * Called from the presenter when a page is set as cover or fails. It shows a different message * depending on the [result]. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageDialog.kt new file mode 100644 index 0000000000..531d12dbb2 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageDialog.kt @@ -0,0 +1,102 @@ +package eu.kanade.tachiyomi.ui.reader + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Photo +import androidx.compose.material.icons.outlined.Save +import androidx.compose.material.icons.outlined.Share +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import eu.kanade.presentation.components.AdaptiveSheet +import eu.kanade.tachiyomi.R +import tachiyomi.presentation.core.components.ActionButton +import tachiyomi.presentation.core.components.material.padding + +@Composable +fun ReaderPageDialog( + onDismissRequest: () -> Unit, + onSetAsCover: () -> Unit, + onShare: () -> Unit, + onSave: () -> Unit, +) { + var showSetCoverDialog by remember { mutableStateOf(false) } + + AdaptiveSheet( + onDismissRequest = onDismissRequest, + ) { + Row( + modifier = Modifier.padding(vertical = 16.dp), + horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), + ) { + ActionButton( + modifier = Modifier.weight(1f), + title = stringResource(R.string.set_as_cover), + icon = Icons.Outlined.Photo, + onClick = { showSetCoverDialog = true }, + ) + ActionButton( + modifier = Modifier.weight(1f), + title = stringResource(R.string.action_share), + icon = Icons.Outlined.Share, + onClick = { + onShare() + onDismissRequest() + }, + ) + ActionButton( + modifier = Modifier.weight(1f), + title = stringResource(R.string.action_save), + icon = Icons.Outlined.Save, + onClick = { + onSave() + onDismissRequest() + }, + ) + } + } + + if (showSetCoverDialog) { + SetCoverDialog( + onConfirm = { + onSetAsCover() + showSetCoverDialog = false + }, + onDismiss = { showSetCoverDialog = false }, + ) + } +} + +@Composable +private fun SetCoverDialog( + onConfirm: () -> Unit, + onDismiss: () -> Unit, +) { + AlertDialog( + text = { + Text(stringResource(R.string.confirm_set_image_as_cover)) + }, + confirmButton = { + TextButton(onClick = onConfirm) { + Text(stringResource(android.R.string.ok)) + } + }, + dismissButton = { + TextButton(onClick = onDismiss) { + Text(stringResource(R.string.action_cancel)) + } + }, + onDismissRequest = onDismiss, + ) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt deleted file mode 100644 index 9dff32a8e9..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt +++ /dev/null @@ -1,62 +0,0 @@ -package eu.kanade.tachiyomi.ui.reader - -import android.view.LayoutInflater -import android.view.View -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.databinding.ReaderPageSheetBinding -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.ui.reader.model.ReaderPage -import eu.kanade.tachiyomi.widget.sheet.BaseBottomSheetDialog - -/** - * Sheet to show when a page is long clicked. - */ -class ReaderPageSheet( - private val activity: ReaderActivity, - private val page: ReaderPage, -) : BaseBottomSheetDialog(activity) { - - private lateinit var binding: ReaderPageSheetBinding - - override fun createView(inflater: LayoutInflater): View { - binding = ReaderPageSheetBinding.inflate(activity.layoutInflater, null, false) - - binding.setAsCover.setOnClickListener { setAsCover() } - binding.share.setOnClickListener { share() } - binding.save.setOnClickListener { save() } - - return binding.root - } - - /** - * Sets the image of this page as the cover of the manga. - */ - private fun setAsCover() { - if (page.status != Page.State.READY) return - - MaterialAlertDialogBuilder(activity) - .setMessage(R.string.confirm_set_image_as_cover) - .setPositiveButton(android.R.string.ok) { _, _ -> - activity.setAsCover(page) - } - .setNegativeButton(R.string.action_cancel, null) - .show() - } - - /** - * Shares the image of this page with external apps. - */ - private fun share() { - activity.shareImage(page) - dismiss() - } - - /** - * Saves the image of this page on external storage. - */ - private fun save() { - activity.saveImage(page) - dismiss() - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt index 6c0d9754ad..cbe527e472 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt @@ -719,12 +719,21 @@ class ReaderViewModel( ) + filenameSuffix } + fun openPageDialog(page: ReaderPage) { + mutableState.update { it.copy(dialog = Dialog.Page(page)) } + } + + fun closeDialog() { + mutableState.update { it.copy(dialog = null) } + } + /** - * Saves the image of this [page] on the pictures directory and notifies the UI of the result. + * Saves the image of the selected page on the pictures directory and notifies the UI of the result. * There's also a notification to allow sharing the image somewhere else or deleting it. */ - fun saveImage(page: ReaderPage) { - if (page.status != Page.State.READY) return + fun saveImage() { + val page = (state.value.dialog as? Dialog.Page)?.page + if (page?.status != Page.State.READY) return val manga = manga ?: return val context = Injekt.get() @@ -758,14 +767,15 @@ class ReaderViewModel( } /** - * Shares the image of this [page] and notifies the UI with the path of the file to share. + * Shares the image of the selected page and notifies the UI with the path of the file to share. * The image must be first copied to the internal partition because there are many possible * formats it can come from, like a zipped chapter, in which case it's not possible to directly * get a path to the file and it has to be decompressed somewhere first. Only the last shared * image will be kept so it won't be taking lots of internal disk space. */ - fun shareImage(page: ReaderPage) { - if (page.status != Page.State.READY) return + fun shareImage() { + val page = (state.value.dialog as? Dialog.Page)?.page + if (page?.status != Page.State.READY) return val manga = manga ?: return val context = Injekt.get() @@ -791,10 +801,11 @@ class ReaderViewModel( } /** - * Sets the image of this [page] as cover and notifies the UI of the result. + * Sets the image of the selected page as cover and notifies the UI of the result. */ - fun setAsCover(page: ReaderPage) { - if (page.status != Page.State.READY) return + fun setAsCover() { + val page = (state.value.dialog as? Dialog.Page)?.page + if (page?.status != Page.State.READY) return val manga = manga ?: return val stream = page.stream ?: return @@ -907,11 +918,16 @@ class ReaderViewModel( * Viewer used to display the pages (pager, webtoon, ...). */ val viewer: Viewer? = null, + val dialog: Dialog? = null, ) { val totalPages: Int get() = viewerChapters?.currChapter?.pages?.size ?: -1 } + sealed class Dialog { + data class Page(val page: ReaderPage) : Dialog() + } + sealed class Event { object ReloadViewerChapters : Event() data class SetOrientation(val orientation: Int) : Event() diff --git a/app/src/main/res/drawable/ic_save_24dp.xml b/app/src/main/res/drawable/ic_save_24dp.xml deleted file mode 100644 index 4435923ad0..0000000000 --- a/app/src/main/res/drawable/ic_save_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/layout/reader_activity.xml b/app/src/main/res/layout/reader_activity.xml index 17a5664e72..1ddf49c3dc 100644 --- a/app/src/main/res/layout/reader_activity.xml +++ b/app/src/main/res/layout/reader_activity.xml @@ -137,4 +137,9 @@ android:layout_height="match_parent" android:visibility="gone" /> + + diff --git a/app/src/main/res/layout/reader_page_sheet.xml b/app/src/main/res/layout/reader_page_sheet.xml deleted file mode 100644 index ab581a65f8..0000000000 --- a/app/src/main/res/layout/reader_page_sheet.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/ActionButton.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/ActionButton.kt new file mode 100644 index 0000000000..54cbd9cddb --- /dev/null +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/ActionButton.kt @@ -0,0 +1,40 @@ +package tachiyomi.presentation.core.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp + +@Composable +fun ActionButton( + modifier: Modifier = Modifier, + title: String, + icon: ImageVector, + onClick: () -> Unit, +) { + TextButton( + modifier = modifier, + onClick = onClick, + ) { + Column( + verticalArrangement = Arrangement.spacedBy(4.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Icon( + imageVector = icon, + contentDescription = null, + ) + Text( + text = title, + textAlign = TextAlign.Center, + ) + } + } +} diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/screens/EmptyScreen.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/screens/EmptyScreen.kt index 6327632399..c3a0cbe795 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/screens/EmptyScreen.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/screens/EmptyScreen.kt @@ -4,17 +4,13 @@ import androidx.annotation.StringRes import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.paddingFromBaseline import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -24,6 +20,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEach +import tachiyomi.presentation.core.components.ActionButton import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.util.secondaryItemAlpha import kotlin.random.Random @@ -96,31 +93,6 @@ fun EmptyScreen( } } -@Composable -private fun ActionButton( - modifier: Modifier = Modifier, - title: String, - icon: ImageVector, - onClick: () -> Unit, -) { - TextButton( - modifier = modifier, - onClick = onClick, - ) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Icon( - imageVector = icon, - contentDescription = null, - ) - Spacer(Modifier.height(4.dp)) - Text( - text = title, - textAlign = TextAlign.Center, - ) - } - } -} - private val ERROR_FACES = listOf( "(・o・;)", "Σ(ಠ_ಠ)",