mirror of
https://github.com/mihonapp/mihon.git
synced 2025-10-30 13:57:57 +01:00
Migrate top reader app bar to Compose
This commit is contained in:
@@ -13,15 +13,10 @@ import android.graphics.Paint
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.KeyEvent
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.MotionEvent
|
||||
import android.view.View.LAYER_TYPE_HARDWARE
|
||||
import android.view.WindowManager
|
||||
import android.view.animation.Animation
|
||||
import android.view.animation.AnimationUtils
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -44,8 +39,7 @@ import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import com.google.android.material.internal.ToolbarUtils
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import com.google.android.material.elevation.SurfaceColors
|
||||
import com.google.android.material.transition.platform.MaterialContainerTransform
|
||||
import dev.chrisbanes.insetter.applyInsetter
|
||||
import eu.kanade.domain.base.BasePreferences
|
||||
@@ -74,13 +68,11 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
|
||||
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
||||
import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale
|
||||
import eu.kanade.tachiyomi.util.system.hasDisplayCutout
|
||||
import eu.kanade.tachiyomi.util.system.isNightMode
|
||||
import eu.kanade.tachiyomi.util.system.toShareIntent
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import eu.kanade.tachiyomi.util.view.setComposeContent
|
||||
import eu.kanade.tachiyomi.widget.listener.SimpleAnimationListener
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
@@ -265,47 +257,6 @@ class ReaderActivity : BaseActivity() {
|
||||
assistUrl?.let { outContent.webUri = it.toUri() }
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.reader, menu)
|
||||
|
||||
val isChapterBookmarked = viewModel.getCurrentChapter()?.chapter?.bookmark ?: false
|
||||
menu.findItem(R.id.action_bookmark).isVisible = !isChapterBookmarked
|
||||
menu.findItem(R.id.action_remove_bookmark).isVisible = isChapterBookmarked
|
||||
|
||||
val isHttpSource = viewModel.getSource() is HttpSource
|
||||
menu.findItem(R.id.action_open_in_web_view).isVisible = isHttpSource
|
||||
menu.findItem(R.id.action_share).isVisible = isHttpSource
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an item of the options menu was clicked. Used to handle clicks on our menu
|
||||
* entries.
|
||||
*/
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.action_open_in_web_view -> {
|
||||
openChapterInWebView()
|
||||
}
|
||||
R.id.action_bookmark -> {
|
||||
viewModel.bookmarkCurrentChapter(true)
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
R.id.action_remove_bookmark -> {
|
||||
viewModel.bookmarkCurrentChapter(false)
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
R.id.action_share -> {
|
||||
assistUrl?.let {
|
||||
val intent = it.toUri().toShareIntent(this, type = "text/plain")
|
||||
startActivity(Intent.createChooser(intent, getString(R.string.action_share)))
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user clicks the back key or the button on the toolbar. The call is
|
||||
* delegated to the presenter.
|
||||
@@ -348,35 +299,12 @@ class ReaderActivity : BaseActivity() {
|
||||
* Initializes the reader menu. It sets up click listeners and the initial visibility.
|
||||
*/
|
||||
private fun initializeMenu() {
|
||||
setSupportActionBar(binding.toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
binding.toolbar.setNavigationOnClickListener {
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
|
||||
binding.toolbar.applyInsetter {
|
||||
type(navigationBars = true, statusBars = true) {
|
||||
margin(top = true, horizontal = true)
|
||||
}
|
||||
}
|
||||
binding.dialogRoot.applyInsetter {
|
||||
type(navigationBars = true) {
|
||||
margin(vertical = true, horizontal = true)
|
||||
}
|
||||
}
|
||||
|
||||
binding.toolbar.setOnClickListener {
|
||||
viewModel.manga?.id?.let { id ->
|
||||
startActivity(
|
||||
Intent(this, MainActivity::class.java).apply {
|
||||
action = Constants.SHORTCUT_MANGA
|
||||
putExtra(Constants.MANGA_EXTRA, id)
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
binding.pageNumber.setComposeContent {
|
||||
val state by viewModel.state.collectAsState()
|
||||
val showPageNumber by viewModel.readerPreferences.showPageNumber().collectAsState()
|
||||
@@ -400,6 +328,9 @@ class ReaderActivity : BaseActivity() {
|
||||
)
|
||||
}
|
||||
|
||||
val isHttpSource = viewModel.getSource() is HttpSource
|
||||
val isFullscreen by readerPreferences.fullscreen().collectAsState()
|
||||
|
||||
val cropBorderPaged by readerPreferences.cropBorders().collectAsState()
|
||||
val cropBorderWebtoon by readerPreferences.cropBordersWebtoon().collectAsState()
|
||||
val isPagerType = ReadingModeType.isPagerType(viewModel.getMangaReadingMode())
|
||||
@@ -407,8 +338,18 @@ class ReaderActivity : BaseActivity() {
|
||||
|
||||
ReaderAppBars(
|
||||
visible = state.menuVisible,
|
||||
viewer = state.viewer,
|
||||
fullscreen = isFullscreen,
|
||||
|
||||
mangaTitle = state.manga?.title,
|
||||
chapterTitle = state.currentChapter?.chapter?.name,
|
||||
navigateUp = onBackPressedDispatcher::onBackPressed,
|
||||
onClickTopAppBar = ::openMangaScreen,
|
||||
bookmarked = state.bookmarked,
|
||||
onToggleBookmarked = viewModel::toggleChapterBookmark,
|
||||
onOpenInWebView = ::openChapterInWebView.takeIf { isHttpSource },
|
||||
onShare = ::shareChapter.takeIf { isHttpSource },
|
||||
|
||||
viewer = state.viewer,
|
||||
onNextChapter = ::loadNextChapter,
|
||||
enabledNext = state.viewerChapters?.nextChapter != null,
|
||||
onPreviousChapter = ::loadPreviousChapter,
|
||||
@@ -435,15 +376,8 @@ class ReaderActivity : BaseActivity() {
|
||||
cropEnabled = cropEnabled,
|
||||
onClickCropBorder = {
|
||||
val enabled = viewModel.toggleCropBorders()
|
||||
|
||||
menuToggleToast?.cancel()
|
||||
menuToggleToast = toast(
|
||||
if (enabled) {
|
||||
R.string.on
|
||||
} else {
|
||||
R.string.off
|
||||
},
|
||||
)
|
||||
menuToggleToast = toast(if (enabled) R.string.on else R.string.off)
|
||||
},
|
||||
onClickSettings = viewModel::openSettingsDialog,
|
||||
)
|
||||
@@ -507,13 +441,9 @@ class ReaderActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
val toolbarBackground = (binding.toolbar.background as MaterialShapeDrawable).apply {
|
||||
elevation = resources.getDimension(R.dimen.m3_sys_elevation_level2)
|
||||
alpha = if (isNightMode()) 230 else 242 // 90% dark 95% light
|
||||
}
|
||||
val toolbarColor = ColorUtils.setAlphaComponent(
|
||||
toolbarBackground.resolvedTintColor,
|
||||
toolbarBackground.alpha,
|
||||
SurfaceColors.SURFACE_2.getColor(this),
|
||||
if (isNightMode()) 230 else 242, // 90% dark 95% light
|
||||
)
|
||||
window.statusBarColor = toolbarColor
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||
@@ -531,35 +461,12 @@ class ReaderActivity : BaseActivity() {
|
||||
viewModel.showMenus(visible)
|
||||
if (visible) {
|
||||
windowInsetsController.show(WindowInsetsCompat.Type.systemBars())
|
||||
binding.readerMenu.isVisible = true
|
||||
|
||||
val toolbarAnimation = AnimationUtils.loadAnimation(this, R.anim.enter_from_top)
|
||||
toolbarAnimation.applySystemAnimatorScale(this)
|
||||
toolbarAnimation.setAnimationListener(
|
||||
object : SimpleAnimationListener() {
|
||||
override fun onAnimationStart(animation: Animation) {
|
||||
// Fix status bar being translucent the first time it's opened.
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
}
|
||||
},
|
||||
)
|
||||
binding.toolbar.startAnimation(toolbarAnimation)
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
} else {
|
||||
if (readerPreferences.fullscreen().get()) {
|
||||
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
|
||||
windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
}
|
||||
|
||||
val toolbarAnimation = AnimationUtils.loadAnimation(this, R.anim.exit_to_top)
|
||||
toolbarAnimation.applySystemAnimatorScale(this)
|
||||
toolbarAnimation.setAnimationListener(
|
||||
object : SimpleAnimationListener() {
|
||||
override fun onAnimationEnd(animation: Animation) {
|
||||
binding.readerMenu.isVisible = false
|
||||
}
|
||||
},
|
||||
)
|
||||
binding.toolbar.startAnimation(toolbarAnimation)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -593,14 +500,24 @@ class ReaderActivity : BaseActivity() {
|
||||
showReadingModeToast(viewModel.getMangaReadingMode())
|
||||
}
|
||||
|
||||
supportActionBar?.title = manga.title
|
||||
|
||||
loadingIndicator = ReaderProgressIndicator(this)
|
||||
binding.readerContainer.addView(loadingIndicator)
|
||||
|
||||
startPostponedEnterTransition()
|
||||
}
|
||||
|
||||
private fun openMangaScreen() {
|
||||
viewModel.manga?.id?.let { id ->
|
||||
startActivity(
|
||||
Intent(this, MainActivity::class.java).apply {
|
||||
action = Constants.SHORTCUT_MANGA
|
||||
putExtra(Constants.MANGA_EXTRA, id)
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun openChapterInWebView() {
|
||||
val manga = viewModel.manga ?: return
|
||||
val source = viewModel.getSource() ?: return
|
||||
@@ -610,6 +527,13 @@ class ReaderActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun shareChapter() {
|
||||
assistUrl?.let {
|
||||
val intent = it.toUri().toShareIntent(this, type = "text/plain")
|
||||
startActivity(Intent.createChooser(intent, getString(R.string.action_share)))
|
||||
}
|
||||
}
|
||||
|
||||
private fun showReadingModeToast(mode: Int) {
|
||||
try {
|
||||
readingModeToast?.cancel()
|
||||
@@ -629,15 +553,6 @@ class ReaderActivity : BaseActivity() {
|
||||
binding.readerContainer.removeView(loadingIndicator)
|
||||
viewModel.state.value.viewer?.setChapters(viewerChapters)
|
||||
|
||||
binding.toolbar.subtitle = viewerChapters.currChapter.chapter.name
|
||||
ToolbarUtils.getSubtitleTextView(binding.toolbar)?.let {
|
||||
it.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||
it.isSelected = true
|
||||
}
|
||||
|
||||
// Invalidate menu to show proper chapter bookmark state
|
||||
invalidateOptionsMenu()
|
||||
|
||||
lifecycleScope.launchIO {
|
||||
viewModel.getChapterUrl()?.let { url ->
|
||||
assistUrl = url
|
||||
@@ -675,7 +590,7 @@ class ReaderActivity : BaseActivity() {
|
||||
*/
|
||||
private fun moveToPageIndex(index: Int) {
|
||||
val viewer = viewModel.state.value.viewer ?: return
|
||||
val currentChapter = viewModel.getCurrentChapter() ?: return
|
||||
val currentChapter = viewModel.state.value.currentChapter ?: return
|
||||
val page = currentChapter.pages?.getOrNull(index) ?: return
|
||||
viewer.moveToPage(page)
|
||||
}
|
||||
|
||||
@@ -298,7 +298,10 @@ class ReaderViewModel @JvmOverloads constructor(
|
||||
it.viewerChapters?.unref()
|
||||
|
||||
chapterToDownload = cancelQueuedDownloads(newChapters.currChapter)
|
||||
it.copy(viewerChapters = newChapters)
|
||||
it.copy(
|
||||
viewerChapters = newChapters,
|
||||
bookmarked = newChapters.currChapter.chapter.bookmark,
|
||||
)
|
||||
}
|
||||
}
|
||||
return newChapters
|
||||
@@ -567,8 +570,8 @@ class ReaderViewModel @JvmOverloads constructor(
|
||||
/**
|
||||
* Returns the currently active chapter.
|
||||
*/
|
||||
fun getCurrentChapter(): ReaderChapter? {
|
||||
return state.value.viewerChapters?.currChapter
|
||||
private fun getCurrentChapter(): ReaderChapter? {
|
||||
return state.value.currentChapter
|
||||
}
|
||||
|
||||
fun getSource() = manga?.source?.let { sourceManager.getOrStub(it) } as? HttpSource
|
||||
@@ -588,9 +591,11 @@ class ReaderViewModel @JvmOverloads constructor(
|
||||
/**
|
||||
* Bookmarks the currently active chapter.
|
||||
*/
|
||||
fun bookmarkCurrentChapter(bookmarked: Boolean) {
|
||||
fun toggleChapterBookmark() {
|
||||
val chapter = getCurrentChapter()?.chapter ?: return
|
||||
chapter.bookmark = bookmarked // Otherwise the bookmark icon doesn't update
|
||||
val bookmarked = !chapter.bookmark
|
||||
chapter.bookmark = bookmarked
|
||||
|
||||
viewModelScope.launchNonCancellable {
|
||||
updateChapter.await(
|
||||
ChapterUpdate(
|
||||
@@ -599,6 +604,12 @@ class ReaderViewModel @JvmOverloads constructor(
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
mutableState.update {
|
||||
it.copy(
|
||||
bookmarked = bookmarked,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -873,6 +884,7 @@ class ReaderViewModel @JvmOverloads constructor(
|
||||
data class State(
|
||||
val manga: Manga? = null,
|
||||
val viewerChapters: ViewerChapters? = null,
|
||||
val bookmarked: Boolean = false,
|
||||
val isLoadingAdjacentChapter: Boolean = false,
|
||||
val currentPage: Int = -1,
|
||||
|
||||
@@ -883,8 +895,11 @@ class ReaderViewModel @JvmOverloads constructor(
|
||||
val dialog: Dialog? = null,
|
||||
val menuVisible: Boolean = false,
|
||||
) {
|
||||
val currentChapter: ReaderChapter?
|
||||
get() = viewerChapters?.currChapter
|
||||
|
||||
val totalPages: Int
|
||||
get() = viewerChapters?.currChapter?.pages?.size ?: -1
|
||||
get() = currentChapter?.pages?.size ?: -1
|
||||
}
|
||||
|
||||
sealed interface Dialog {
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
package eu.kanade.tachiyomi.widget.listener
|
||||
|
||||
import android.view.animation.Animation
|
||||
|
||||
open class SimpleAnimationListener : Animation.AnimationListener {
|
||||
override fun onAnimationRepeat(animation: Animation) {}
|
||||
|
||||
override fun onAnimationEnd(animation: Animation) {}
|
||||
|
||||
override fun onAnimationStart(animation: Animation) {}
|
||||
}
|
||||
Reference in New Issue
Block a user