Update reader ui

This commit is contained in:
Koitharu
2024-04-13 12:57:40 +03:00
parent 61e02dd827
commit bddb8431c5
11 changed files with 154 additions and 107 deletions

View File

@@ -45,11 +45,9 @@ import org.koitharu.kotatsu.core.util.IdlingDetector
import org.koitharu.kotatsu.core.util.ShareHelper import org.koitharu.kotatsu.core.util.ShareHelper
import org.koitharu.kotatsu.core.util.ext.hasGlobalPoint import org.koitharu.kotatsu.core.util.ext.hasGlobalPoint
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
import org.koitharu.kotatsu.core.util.ext.isRtl
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.postDelayed import org.koitharu.kotatsu.core.util.ext.postDelayed
import org.koitharu.kotatsu.core.util.ext.setValueRounded
import org.koitharu.kotatsu.core.util.ext.zipWithPrevious import org.koitharu.kotatsu.core.util.ext.zipWithPrevious
import org.koitharu.kotatsu.databinding.ActivityReaderBinding import org.koitharu.kotatsu.databinding.ActivityReaderBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
@@ -105,6 +103,7 @@ class ReaderActivity :
private var gestureInsets: Insets = Insets.NONE private var gestureInsets: Insets = Insets.NONE
private lateinit var readerManager: ReaderManager private lateinit var readerManager: ReaderManager
private val hideUiRunnable = Runnable { setUiIsVisible(false) } private val hideUiRunnable = Runnable { setUiIsVisible(false) }
private lateinit var bottomMenuProvider: ReaderBottomMenuProvider
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -114,9 +113,8 @@ class ReaderActivity :
touchHelper = TapGridDispatcher(this, this) touchHelper = TapGridDispatcher(this, this)
scrollTimer = scrollTimerFactory.create(this, this) scrollTimer = scrollTimerFactory.create(this, this)
controlDelegate = ReaderControlDelegate(resources, settings, tapGridSettings, this) controlDelegate = ReaderControlDelegate(resources, settings, tapGridSettings, this)
viewBinding.slider.setLabelFormatter(PageLabelFormatter()) bottomMenuProvider = ReaderBottomMenuProvider(this, readerManager, viewModel, this)
viewBinding.zoomControl.listener = this viewBinding.zoomControl.listener = this
ReaderSliderListener(viewModel, this).attachToSlider(viewBinding.slider)
insetsDelegate.interceptingWindowInsetsListener = this insetsDelegate.interceptingWindowInsetsListener = this
idlingDetector.bindToLifecycle(this) idlingDetector.bindToLifecycle(this)
@@ -142,11 +140,10 @@ class ReaderActivity :
viewModel.content.observe(this) { viewModel.content.observe(this) {
onLoadingStateChanged(viewModel.isLoading.value) onLoadingStateChanged(viewModel.isLoading.value)
} }
viewModel.incognitoMode.observe(this, MenuInvalidator(this))
viewModel.isScreenshotsBlockEnabled.observe(this, this::setWindowSecure) viewModel.isScreenshotsBlockEnabled.observe(this, this::setWindowSecure)
viewModel.isKeepScreenOnEnabled.observe(this, this::setKeepScreenOn) viewModel.isKeepScreenOnEnabled.observe(this, this::setKeepScreenOn)
viewModel.isInfoBarEnabled.observe(this, ::onReaderBarChanged) viewModel.isInfoBarEnabled.observe(this, ::onReaderBarChanged)
viewModel.isBookmarkAdded.observe(this, MenuInvalidator(viewBinding.toolbarBottom)) viewModel.isBookmarkAdded.observe(this, MenuInvalidator(this))
viewModel.onShowToast.observeEvent(this) { msgId -> viewModel.onShowToast.observeEvent(this) { msgId ->
Snackbar.make(viewBinding.container, msgId, Snackbar.LENGTH_SHORT) Snackbar.make(viewBinding.container, msgId, Snackbar.LENGTH_SHORT)
.setAnchorView(viewBinding.appbarBottom) .setAnchorView(viewBinding.appbarBottom)
@@ -156,7 +153,8 @@ class ReaderActivity :
viewBinding.zoomControl.isVisible = it viewBinding.zoomControl.isVisible = it
} }
addMenuProvider(ReaderTopMenuProvider(this, viewModel)) addMenuProvider(ReaderTopMenuProvider(this, viewModel))
viewBinding.toolbarBottom.addMenuProvider(ReaderBottomMenuProvider(this, readerManager, viewModel)) viewBinding.toolbarBottom.addMenuProvider(bottomMenuProvider)
onBackPressedDispatcher.addCallback(bottomMenuProvider)
} }
override fun onActivityResult(result: Uri?) { override fun onActivityResult(result: Uri?) {
@@ -198,10 +196,9 @@ class ReaderActivity :
if (readerManager.currentMode != mode) { if (readerManager.currentMode != mode) {
readerManager.replace(mode) readerManager.replace(mode)
} }
if (viewBinding.appbarTop.isVisible) { if (viewBinding.appbarTop.isVisible && !bottomMenuProvider.isSliderExpanded()) {
lifecycle.postDelayed(TimeUnit.SECONDS.toMillis(1), hideUiRunnable) lifecycle.postDelayed(TimeUnit.SECONDS.toMillis(1), hideUiRunnable)
} }
viewBinding.slider.isRtl = mode == ReaderMode.REVERSED
} }
private fun onLoadingStateChanged(isLoading: Boolean) { private fun onLoadingStateChanged(isLoading: Boolean) {
@@ -213,6 +210,7 @@ class ReaderActivity :
viewBinding.toastView.hide() viewBinding.toastView.hide()
} }
viewBinding.toolbarBottom.invalidateMenu() viewBinding.toolbarBottom.invalidateMenu()
invalidateMenu()
} }
override fun onGridTouch(area: TapGridArea): Boolean { override fun onGridTouch(area: TapGridArea): Boolean {
@@ -330,6 +328,9 @@ class ReaderActivity :
viewBinding.infoBar.isGone = isUiVisible || (!viewModel.isInfoBarEnabled.value) viewBinding.infoBar.isGone = isUiVisible || (!viewModel.isInfoBarEnabled.value)
viewBinding.infoBar.isTimeVisible = isFullscreen viewBinding.infoBar.isTimeVisible = isFullscreen
systemUiController.setSystemUiVisible(isUiVisible || !isFullscreen) systemUiController.setSystemUiVisible(isUiVisible || !isFullscreen)
if (!isUiVisible) {
bottomMenuProvider.collapseSlider()
}
} }
} }
@@ -394,26 +395,21 @@ class ReaderActivity :
private fun onUiStateChanged(pair: Pair<ReaderUiState?, ReaderUiState?>) { private fun onUiStateChanged(pair: Pair<ReaderUiState?, ReaderUiState?>) {
val (previous: ReaderUiState?, uiState: ReaderUiState?) = pair val (previous: ReaderUiState?, uiState: ReaderUiState?) = pair
title = uiState?.resolveTitle(this) ?: getString(R.string.loading_) title = uiState?.mangaName ?: getString(R.string.loading_)
viewBinding.infoBar.update(uiState) supportActionBar?.subtitle = uiState?.chapterName
if (uiState == null) { with(viewBinding.toolbarBottom) {
supportActionBar?.subtitle = null title = uiState?.resolveSummary(context)
viewBinding.slider.isVisible = false subtitle = uiState?.resolveSubtitle(context)
return
} }
supportActionBar?.subtitle = uiState.chapterName viewBinding.infoBar.update(uiState)
if (previous?.chapterName != null && uiState.chapterName != previous.chapterName) { if (!bottomMenuProvider.updateState(uiState)) {
viewBinding.toolbarBottom.invalidateMenu()
}
if (uiState != null && previous?.chapterName != null && uiState.chapterName != previous.chapterName) {
if (!uiState.chapterName.isNullOrEmpty()) { if (!uiState.chapterName.isNullOrEmpty()) {
viewBinding.toastView.showTemporary(uiState.chapterName, TOAST_DURATION) viewBinding.toastView.showTemporary(uiState.chapterName, TOAST_DURATION)
} }
} }
if (uiState.isSliderAvailable()) {
viewBinding.slider.valueTo = uiState.totalPages.toFloat() - 1
viewBinding.slider.setValueRounded(uiState.currentPage.toFloat())
viewBinding.slider.isVisible = true
} else {
viewBinding.slider.isVisible = false
}
} }
class IntentBuilder(context: Context) { class IntentBuilder(context: Context) {

View File

@@ -3,18 +3,29 @@ package org.koitharu.kotatsu.reader.ui
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import androidx.activity.OnBackPressedCallback
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.google.android.material.slider.LabelFormatter
import com.google.android.material.slider.Slider
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.core.util.ext.isRtl
import org.koitharu.kotatsu.core.util.ext.setValueRounded
import org.koitharu.kotatsu.details.ui.pager.ChaptersPagesSheet import org.koitharu.kotatsu.details.ui.pager.ChaptersPagesSheet
import org.koitharu.kotatsu.reader.ui.config.ReaderConfigSheet import org.koitharu.kotatsu.reader.ui.config.ReaderConfigSheet
import org.koitharu.kotatsu.reader.ui.pager.ReaderUiState
import org.koitharu.kotatsu.settings.SettingsActivity import org.koitharu.kotatsu.settings.SettingsActivity
import java.lang.ref.WeakReference
class ReaderBottomMenuProvider( class ReaderBottomMenuProvider(
private val activity: FragmentActivity, private val activity: FragmentActivity,
private val readerManager: ReaderManager, private val readerManager: ReaderManager,
private val viewModel: ReaderViewModel, private val viewModel: ReaderViewModel,
) : MenuProvider { private val callback: ReaderNavigationCallback,
) : OnBackPressedCallback(false), MenuProvider, MenuItem.OnActionExpandListener {
private var expandedItemRef: WeakReference<MenuItem>? = null
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.opt_reader_bottom, menu) menuInflater.inflate(R.menu.opt_reader_bottom, menu)
@@ -22,15 +33,21 @@ class ReaderBottomMenuProvider(
} }
override fun onPrepareMenu(menu: Menu) { override fun onPrepareMenu(menu: Menu) {
val shouldExpandSlider = expandedItemRef != null
val hasPages = viewModel.content.value.pages.isNotEmpty() val hasPages = viewModel.content.value.pages.isNotEmpty()
menu.findItem(R.id.action_pages_thumbs).isVisible = hasPages menu.findItem(R.id.action_pages_thumbs).isVisible = hasPages
val bookmarkItem = menu.findItem(R.id.action_bookmark) ?: return menu.findItem(R.id.action_slider)?.run {
bookmarkItem.isVisible = hasPages val state = viewModel.uiState.value?.takeIf { it.isSliderAvailable() }
if (hasPages) { isVisible = state != null
val hasBookmark = viewModel.isBookmarkAdded.value setOnActionExpandListener(this@ReaderBottomMenuProvider)
bookmarkItem.setTitle(if (hasBookmark) R.string.bookmark_remove else R.string.bookmark_add) if (state != null) {
bookmarkItem.setIcon(if (hasBookmark) R.drawable.ic_bookmark_added else R.drawable.ic_bookmark) (actionView as? Slider)?.setupPagesSlider(state)
}
if (shouldExpandSlider) {
expandActionView()
}
} }
} }
@@ -46,15 +63,6 @@ class ReaderBottomMenuProvider(
true true
} }
R.id.action_bookmark -> {
if (viewModel.isBookmarkAdded.value) {
viewModel.removeBookmark()
} else {
viewModel.addBookmark()
}
true
}
R.id.action_options -> { R.id.action_options -> {
viewModel.saveCurrentState(readerManager.currentReader?.getCurrentState()) viewModel.saveCurrentState(readerManager.currentReader?.getCurrentState())
val currentMode = readerManager.currentMode ?: return false val currentMode = readerManager.currentMode ?: return false
@@ -65,4 +73,47 @@ class ReaderBottomMenuProvider(
else -> false else -> false
} }
} }
override fun handleOnBackPressed() {
expandedItemRef?.get()?.collapseActionView()
}
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
expandedItemRef = WeakReference(item)
isEnabled = true
return true
}
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
expandedItemRef = null
isEnabled = false
return true
}
fun collapseSlider() {
expandedItemRef?.get()?.collapseActionView()
}
fun isSliderExpanded(): Boolean {
return expandedItemRef?.get()?.isActionViewExpanded == true
}
fun updateState(state: ReaderUiState?): Boolean {
if (state == null || !state.isSliderAvailable()) {
return false
}
val slider = (expandedItemRef?.get()?.actionView as? Slider) ?: return false
slider.valueTo = state.totalPages.toFloat() - 1
slider.setValueRounded(state.currentPage.toFloat())
return true
}
private fun Slider.setupPagesSlider(state: ReaderUiState) {
isRtl = viewModel.readerMode.value == ReaderMode.REVERSED
valueTo = state.totalPages.toFloat() - 1
setValueRounded(state.currentPage.toFloat())
labelBehavior = LabelFormatter.LABEL_FLOATING
setLabelFormatter(PageLabelFormatter())
ReaderSliderListener(viewModel, callback).attachToSlider(this)
}
} }

View File

@@ -169,13 +169,7 @@ class ReaderInfoBarView @JvmOverloads constructor(
fun update(state: ReaderUiState?) { fun update(state: ReaderUiState?) {
text = if (state != null) { text = if (state != null) {
context.getString( state.resolveSummary(context) + if (state.percent in 0f..1f) {
R.string.reader_info_pattern,
state.chapterNumber,
state.chaptersTotal,
state.currentPage + 1,
state.totalPages,
) + if (state.percent in 0f..1f) {
" " + context.getString(R.string.percent_string_pattern, (state.percent * 100).format()) " " + context.getString(R.string.percent_string_pattern, (state.percent * 100).format())
} else { } else {
"" ""

View File

@@ -5,10 +5,7 @@ import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.util.ext.DIALOG_THEME_CENTERED
import org.koitharu.kotatsu.details.ui.pager.ChaptersPagesSheet
class ReaderTopMenuProvider( class ReaderTopMenuProvider(
private val activity: FragmentActivity, private val activity: FragmentActivity,
@@ -20,31 +17,29 @@ class ReaderTopMenuProvider(
} }
override fun onPrepareMenu(menu: Menu) { override fun onPrepareMenu(menu: Menu) {
menu.findItem(R.id.action_incognito)?.isVisible = viewModel.incognitoMode.value menu.findItem(R.id.action_bookmark)?.let { bookmarkItem ->
val hasPages = viewModel.content.value.pages.isNotEmpty()
bookmarkItem.isVisible = hasPages
if (hasPages) {
val hasBookmark = viewModel.isBookmarkAdded.value
bookmarkItem.setTitle(if (hasBookmark) R.string.bookmark_remove else R.string.bookmark_add)
bookmarkItem.setIcon(if (hasBookmark) R.drawable.ic_bookmark_added else R.drawable.ic_bookmark)
}
}
} }
override fun onMenuItemSelected(menuItem: MenuItem): Boolean { override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return when (menuItem.itemId) { return when (menuItem.itemId) {
R.id.action_chapters -> { R.id.action_bookmark -> {
ChaptersPagesSheet.show(activity.supportFragmentManager, true, ChaptersPagesSheet.TAB_CHAPTERS) if (viewModel.isBookmarkAdded.value) {
true viewModel.removeBookmark()
} } else {
viewModel.addBookmark()
R.id.action_incognito -> { }
showIncognitoModeDialog()
true true
} }
else -> false else -> false
} }
} }
private fun showIncognitoModeDialog() {
MaterialAlertDialogBuilder(activity, DIALOG_THEME_CENTERED)
.setIcon(R.drawable.ic_incognito)
.setTitle(R.string.incognito_mode)
.setMessage(R.string.incognito_mode_hint)
.setPositiveButton(R.string.got_it, null)
.show()
}
} }

View File

@@ -431,6 +431,7 @@ class ReaderViewModel @Inject constructor(
currentPage = state.page, currentPage = state.page,
isSliderEnabled = settings.isReaderSliderEnabled, isSliderEnabled = settings.isReaderSliderEnabled,
percent = computePercent(state.chapterId, state.page), percent = computePercent(state.chapterId, state.page),
incognito = incognitoMode.value,
) )
uiState.value = newState uiState.value = newState
if (!incognitoMode.value) { if (!incognitoMode.value) {

View File

@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.reader.ui.pager
import android.content.Context import android.content.Context
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.parsers.util.format
data class ReaderUiState( data class ReaderUiState(
val mangaName: String?, val mangaName: String?,
@@ -12,6 +13,7 @@ data class ReaderUiState(
val currentPage: Int, val currentPage: Int,
val totalPages: Int, val totalPages: Int,
val percent: Float, val percent: Float,
val incognito: Boolean,
private val isSliderEnabled: Boolean, private val isSliderEnabled: Boolean,
) { ) {
@@ -19,9 +21,27 @@ data class ReaderUiState(
return isSliderEnabled && totalPages > 1 && currentPage < totalPages return isSliderEnabled && totalPages > 1 && currentPage < totalPages
} }
fun resolveTitle(context: Context): String? = when { fun resolveSubtitle(context: Context): String? {
mangaName == null -> null val firstPart = branch
branch == null -> mangaName val secondPart = if (incognito) {
else -> context.getString(R.string.manga_branch_title_template, mangaName, branch) context.getString(R.string.incognito_mode)
} else if (percent in 0f..1f) {
context.getString(R.string.percent_string_pattern, (percent * 100).format())
} else {
null
}
return if (firstPart != null && secondPart != null) {
context.getString(R.string.download_summary_pattern, firstPart, secondPart)
} else {
firstPart ?: secondPart
}
} }
fun resolveSummary(context: Context) = context.getString(
R.string.reader_info_pattern,
chapterNumber,
chaptersTotal,
currentPage + 1,
totalPages,
)
} }

View File

@@ -0,0 +1,11 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000000"
android:pathData="M21,9L17,5V8H10V10H17V13M7,11L3,15L7,19V16H14V14H7V11Z" />
</vector>

View File

@@ -60,15 +60,6 @@
android:layout_weight="1" android:layout_weight="1"
tools:menu="@menu/opt_reader_bottom"> tools:menu="@menu/opt_reader_bottom">
<com.google.android.material.slider.Slider
android:id="@+id/slider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:stepSize="1"
android:valueFrom="0"
app:labelBehavior="floating"
app:tickVisible="false" />
</com.google.android.material.appbar.MaterialToolbar> </com.google.android.material.appbar.MaterialToolbar>
</LinearLayout> </LinearLayout>

View File

@@ -58,23 +58,14 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom" android:layout_gravity="bottom"
android:layout_margin="8dp" android:layout_margin="8dp"
app:cardBackgroundColor="?colorBackgroundFloating"
app:layout_insetEdge="bottom"> app:layout_insetEdge="bottom">
<com.google.android.material.appbar.MaterialToolbar <com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_bottom" android:id="@+id/toolbar_bottom"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:menu="@menu/opt_reader_bottom"> tools:menu="@menu/opt_reader_bottom" />
<com.google.android.material.slider.Slider
android:id="@+id/slider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:stepSize="1.0"
android:valueFrom="0"
app:labelBehavior="floating" />
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>

View File

@@ -6,18 +6,21 @@
tools:ignore="AlwaysShowAction"> tools:ignore="AlwaysShowAction">
<item <item
android:id="@+id/action_bookmark" android:id="@+id/action_slider"
android:icon="@drawable/ic_bookmark" android:icon="@drawable/ic_slider"
android:title="@string/bookmark_add" android:title="@string/reader_slider"
android:visible="false" android:visible="false"
app:showAsAction="always" /> app:actionViewClass="com.google.android.material.slider.Slider"
app:showAsAction="always|collapseActionView"
tools:visible="true" />
<item <item
android:id="@+id/action_pages_thumbs" android:id="@+id/action_pages_thumbs"
android:icon="@drawable/ic_grid" android:icon="@drawable/ic_grid"
android:title="@string/pages" android:title="@string/pages"
android:visible="false" android:visible="false"
app:showAsAction="always" /> app:showAsAction="always"
tools:visible="true" />
<item <item
android:id="@+id/action_options" android:id="@+id/action_options"

View File

@@ -6,17 +6,11 @@
tools:ignore="AlwaysShowAction"> tools:ignore="AlwaysShowAction">
<item <item
android:id="@+id/action_chapters" android:id="@+id/action_bookmark"
android:icon="@drawable/ic_expand_more" android:icon="@drawable/ic_bookmark"
android:orderInCategory="0" android:title="@string/bookmark_add"
android:title="@string/chapters"
app:showAsAction="always" />
<item
android:id="@+id/action_incognito"
android:icon="@drawable/ic_incognito"
android:title="@string/incognito_mode"
android:visible="false" android:visible="false"
app:showAsAction="always" /> app:showAsAction="always"
tools:visible="true" />
</menu> </menu>