From bddb8431c572c398ef4e431497c911c9404790dc Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 13 Apr 2024 12:57:40 +0300 Subject: [PATCH] Update reader ui --- .../kotatsu/reader/ui/ReaderActivity.kt | 44 +++++----- .../reader/ui/ReaderBottomMenuProvider.kt | 83 +++++++++++++++---- .../kotatsu/reader/ui/ReaderInfoBarView.kt | 8 +- .../reader/ui/ReaderTopMenuProvider.kt | 35 ++++---- .../kotatsu/reader/ui/ReaderViewModel.kt | 1 + .../kotatsu/reader/ui/pager/ReaderUiState.kt | 28 ++++++- app/src/main/res/drawable/ic_slider.xml | 11 +++ .../layout-w600dp-land/activity_reader.xml | 9 -- app/src/main/res/layout/activity_reader.xml | 13 +-- app/src/main/res/menu/opt_reader_bottom.xml | 13 +-- app/src/main/res/menu/opt_reader_top.xml | 16 ++-- 11 files changed, 154 insertions(+), 107 deletions(-) create mode 100644 app/src/main/res/drawable/ic_slider.xml diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt index d60cd7f57..98d231a81 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt @@ -45,11 +45,9 @@ import org.koitharu.kotatsu.core.util.IdlingDetector import org.koitharu.kotatsu.core.util.ShareHelper import org.koitharu.kotatsu.core.util.ext.hasGlobalPoint 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.observeEvent 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.databinding.ActivityReaderBinding import org.koitharu.kotatsu.details.ui.DetailsActivity @@ -105,6 +103,7 @@ class ReaderActivity : private var gestureInsets: Insets = Insets.NONE private lateinit var readerManager: ReaderManager private val hideUiRunnable = Runnable { setUiIsVisible(false) } + private lateinit var bottomMenuProvider: ReaderBottomMenuProvider override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -114,9 +113,8 @@ class ReaderActivity : touchHelper = TapGridDispatcher(this, this) scrollTimer = scrollTimerFactory.create(this, this) controlDelegate = ReaderControlDelegate(resources, settings, tapGridSettings, this) - viewBinding.slider.setLabelFormatter(PageLabelFormatter()) + bottomMenuProvider = ReaderBottomMenuProvider(this, readerManager, viewModel, this) viewBinding.zoomControl.listener = this - ReaderSliderListener(viewModel, this).attachToSlider(viewBinding.slider) insetsDelegate.interceptingWindowInsetsListener = this idlingDetector.bindToLifecycle(this) @@ -142,11 +140,10 @@ class ReaderActivity : viewModel.content.observe(this) { onLoadingStateChanged(viewModel.isLoading.value) } - viewModel.incognitoMode.observe(this, MenuInvalidator(this)) viewModel.isScreenshotsBlockEnabled.observe(this, this::setWindowSecure) viewModel.isKeepScreenOnEnabled.observe(this, this::setKeepScreenOn) viewModel.isInfoBarEnabled.observe(this, ::onReaderBarChanged) - viewModel.isBookmarkAdded.observe(this, MenuInvalidator(viewBinding.toolbarBottom)) + viewModel.isBookmarkAdded.observe(this, MenuInvalidator(this)) viewModel.onShowToast.observeEvent(this) { msgId -> Snackbar.make(viewBinding.container, msgId, Snackbar.LENGTH_SHORT) .setAnchorView(viewBinding.appbarBottom) @@ -156,7 +153,8 @@ class ReaderActivity : viewBinding.zoomControl.isVisible = it } addMenuProvider(ReaderTopMenuProvider(this, viewModel)) - viewBinding.toolbarBottom.addMenuProvider(ReaderBottomMenuProvider(this, readerManager, viewModel)) + viewBinding.toolbarBottom.addMenuProvider(bottomMenuProvider) + onBackPressedDispatcher.addCallback(bottomMenuProvider) } override fun onActivityResult(result: Uri?) { @@ -198,10 +196,9 @@ class ReaderActivity : if (readerManager.currentMode != mode) { readerManager.replace(mode) } - if (viewBinding.appbarTop.isVisible) { + if (viewBinding.appbarTop.isVisible && !bottomMenuProvider.isSliderExpanded()) { lifecycle.postDelayed(TimeUnit.SECONDS.toMillis(1), hideUiRunnable) } - viewBinding.slider.isRtl = mode == ReaderMode.REVERSED } private fun onLoadingStateChanged(isLoading: Boolean) { @@ -213,6 +210,7 @@ class ReaderActivity : viewBinding.toastView.hide() } viewBinding.toolbarBottom.invalidateMenu() + invalidateMenu() } override fun onGridTouch(area: TapGridArea): Boolean { @@ -330,6 +328,9 @@ class ReaderActivity : viewBinding.infoBar.isGone = isUiVisible || (!viewModel.isInfoBarEnabled.value) viewBinding.infoBar.isTimeVisible = isFullscreen systemUiController.setSystemUiVisible(isUiVisible || !isFullscreen) + if (!isUiVisible) { + bottomMenuProvider.collapseSlider() + } } } @@ -394,26 +395,21 @@ class ReaderActivity : private fun onUiStateChanged(pair: Pair) { val (previous: ReaderUiState?, uiState: ReaderUiState?) = pair - title = uiState?.resolveTitle(this) ?: getString(R.string.loading_) - viewBinding.infoBar.update(uiState) - if (uiState == null) { - supportActionBar?.subtitle = null - viewBinding.slider.isVisible = false - return + title = uiState?.mangaName ?: getString(R.string.loading_) + supportActionBar?.subtitle = uiState?.chapterName + with(viewBinding.toolbarBottom) { + title = uiState?.resolveSummary(context) + subtitle = uiState?.resolveSubtitle(context) } - supportActionBar?.subtitle = uiState.chapterName - if (previous?.chapterName != null && uiState.chapterName != previous.chapterName) { + viewBinding.infoBar.update(uiState) + if (!bottomMenuProvider.updateState(uiState)) { + viewBinding.toolbarBottom.invalidateMenu() + } + if (uiState != null && previous?.chapterName != null && uiState.chapterName != previous.chapterName) { if (!uiState.chapterName.isNullOrEmpty()) { 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) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderBottomMenuProvider.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderBottomMenuProvider.kt index 2c8db46ed..c7ddc9f22 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderBottomMenuProvider.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderBottomMenuProvider.kt @@ -3,18 +3,29 @@ package org.koitharu.kotatsu.reader.ui import android.view.Menu import android.view.MenuInflater import android.view.MenuItem +import androidx.activity.OnBackPressedCallback import androidx.core.view.MenuProvider 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.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.reader.ui.config.ReaderConfigSheet +import org.koitharu.kotatsu.reader.ui.pager.ReaderUiState import org.koitharu.kotatsu.settings.SettingsActivity +import java.lang.ref.WeakReference class ReaderBottomMenuProvider( private val activity: FragmentActivity, private val readerManager: ReaderManager, private val viewModel: ReaderViewModel, -) : MenuProvider { + private val callback: ReaderNavigationCallback, +) : OnBackPressedCallback(false), MenuProvider, MenuItem.OnActionExpandListener { + + private var expandedItemRef: WeakReference? = null override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { menuInflater.inflate(R.menu.opt_reader_bottom, menu) @@ -22,15 +33,21 @@ class ReaderBottomMenuProvider( } override fun onPrepareMenu(menu: Menu) { + val shouldExpandSlider = expandedItemRef != null + val hasPages = viewModel.content.value.pages.isNotEmpty() menu.findItem(R.id.action_pages_thumbs).isVisible = hasPages - val bookmarkItem = menu.findItem(R.id.action_bookmark) ?: return - 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) + menu.findItem(R.id.action_slider)?.run { + val state = viewModel.uiState.value?.takeIf { it.isSliderAvailable() } + isVisible = state != null + setOnActionExpandListener(this@ReaderBottomMenuProvider) + if (state != null) { + (actionView as? Slider)?.setupPagesSlider(state) + } + if (shouldExpandSlider) { + expandActionView() + } } } @@ -46,15 +63,6 @@ class ReaderBottomMenuProvider( true } - R.id.action_bookmark -> { - if (viewModel.isBookmarkAdded.value) { - viewModel.removeBookmark() - } else { - viewModel.addBookmark() - } - true - } - R.id.action_options -> { viewModel.saveCurrentState(readerManager.currentReader?.getCurrentState()) val currentMode = readerManager.currentMode ?: return false @@ -65,4 +73,47 @@ class ReaderBottomMenuProvider( 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) + } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderInfoBarView.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderInfoBarView.kt index 7d590311d..01f6488a6 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderInfoBarView.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderInfoBarView.kt @@ -169,13 +169,7 @@ class ReaderInfoBarView @JvmOverloads constructor( fun update(state: ReaderUiState?) { text = if (state != null) { - context.getString( - R.string.reader_info_pattern, - state.chapterNumber, - state.chaptersTotal, - state.currentPage + 1, - state.totalPages, - ) + if (state.percent in 0f..1f) { + state.resolveSummary(context) + if (state.percent in 0f..1f) { " " + context.getString(R.string.percent_string_pattern, (state.percent * 100).format()) } else { "" diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderTopMenuProvider.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderTopMenuProvider.kt index 496f560dc..794be3671 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderTopMenuProvider.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderTopMenuProvider.kt @@ -5,10 +5,7 @@ import android.view.MenuInflater import android.view.MenuItem import androidx.core.view.MenuProvider import androidx.fragment.app.FragmentActivity -import com.google.android.material.dialog.MaterialAlertDialogBuilder 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( private val activity: FragmentActivity, @@ -20,31 +17,29 @@ class ReaderTopMenuProvider( } 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 { return when (menuItem.itemId) { - R.id.action_chapters -> { - ChaptersPagesSheet.show(activity.supportFragmentManager, true, ChaptersPagesSheet.TAB_CHAPTERS) - true - } - - R.id.action_incognito -> { - showIncognitoModeDialog() + R.id.action_bookmark -> { + if (viewModel.isBookmarkAdded.value) { + viewModel.removeBookmark() + } else { + viewModel.addBookmark() + } true } 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() - } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt index 119952b73..5c1bade81 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt @@ -431,6 +431,7 @@ class ReaderViewModel @Inject constructor( currentPage = state.page, isSliderEnabled = settings.isReaderSliderEnabled, percent = computePercent(state.chapterId, state.page), + incognito = incognitoMode.value, ) uiState.value = newState if (!incognitoMode.value) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/ReaderUiState.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/ReaderUiState.kt index 650c4439a..3c5766dcb 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/ReaderUiState.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/ReaderUiState.kt @@ -2,6 +2,7 @@ package org.koitharu.kotatsu.reader.ui.pager import android.content.Context import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.parsers.util.format data class ReaderUiState( val mangaName: String?, @@ -12,6 +13,7 @@ data class ReaderUiState( val currentPage: Int, val totalPages: Int, val percent: Float, + val incognito: Boolean, private val isSliderEnabled: Boolean, ) { @@ -19,9 +21,27 @@ data class ReaderUiState( return isSliderEnabled && totalPages > 1 && currentPage < totalPages } - fun resolveTitle(context: Context): String? = when { - mangaName == null -> null - branch == null -> mangaName - else -> context.getString(R.string.manga_branch_title_template, mangaName, branch) + fun resolveSubtitle(context: Context): String? { + val firstPart = branch + val secondPart = if (incognito) { + 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, + ) } diff --git a/app/src/main/res/drawable/ic_slider.xml b/app/src/main/res/drawable/ic_slider.xml new file mode 100644 index 000000000..cfef5e738 --- /dev/null +++ b/app/src/main/res/drawable/ic_slider.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/layout-w600dp-land/activity_reader.xml b/app/src/main/res/layout-w600dp-land/activity_reader.xml index e6c8c5317..939771332 100644 --- a/app/src/main/res/layout-w600dp-land/activity_reader.xml +++ b/app/src/main/res/layout-w600dp-land/activity_reader.xml @@ -60,15 +60,6 @@ android:layout_weight="1" tools:menu="@menu/opt_reader_bottom"> - - diff --git a/app/src/main/res/layout/activity_reader.xml b/app/src/main/res/layout/activity_reader.xml index a1e040eef..a6fcad57f 100644 --- a/app/src/main/res/layout/activity_reader.xml +++ b/app/src/main/res/layout/activity_reader.xml @@ -58,23 +58,14 @@ android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_margin="8dp" + app:cardBackgroundColor="?colorBackgroundFloating" app:layout_insetEdge="bottom"> - - - - + tools:menu="@menu/opt_reader_bottom" /> diff --git a/app/src/main/res/menu/opt_reader_bottom.xml b/app/src/main/res/menu/opt_reader_bottom.xml index d900b0c37..6d93bbe85 100644 --- a/app/src/main/res/menu/opt_reader_bottom.xml +++ b/app/src/main/res/menu/opt_reader_bottom.xml @@ -6,18 +6,21 @@ tools:ignore="AlwaysShowAction"> + app:actionViewClass="com.google.android.material.slider.Slider" + app:showAsAction="always|collapseActionView" + tools:visible="true" /> + app:showAsAction="always" + tools:visible="true" /> - - + app:showAsAction="always" + tools:visible="true" />