From 9fde0106be5035972deaaf920500559309d79626 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 2 Nov 2025 11:05:14 +0200 Subject: [PATCH] Fix code formatting --- .../kotatsu/reader/ui/ReaderActivity.kt | 959 +++++++++--------- .../reader/ui/config/ReaderConfigSheet.kt | 444 ++++---- 2 files changed, 704 insertions(+), 699 deletions(-) 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 972500def..05599a718 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 @@ -3,6 +3,7 @@ package org.koitharu.kotatsu.reader.ui import android.app.assist.AssistContent import android.content.DialogInterface import android.content.Intent +import android.content.res.Configuration import android.os.Bundle import android.view.Gravity import android.view.KeyEvent @@ -26,17 +27,16 @@ import androidx.transition.TransitionManager import androidx.transition.TransitionSet import androidx.window.layout.FoldingFeature import androidx.window.layout.WindowInfoTracker -import android.content.res.Configuration import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.koitharu.kotatsu.R @@ -75,296 +75,283 @@ import androidx.appcompat.R as appcompatR @AndroidEntryPoint class ReaderActivity : - BaseFullscreenActivity(), - TapGridDispatcher.OnGridTouchListener, - ReaderConfigSheet.Callback, - ReaderControlDelegate.OnInteractionListener, - ReaderNavigationCallback, - IdlingDetector.Callback, - ZoomControl.ZoomControlListener, - View.OnClickListener, - ScrollTimerControlView.OnVisibilityChangeListener { + BaseFullscreenActivity(), + TapGridDispatcher.OnGridTouchListener, + ReaderConfigSheet.Callback, + ReaderControlDelegate.OnInteractionListener, + ReaderNavigationCallback, + IdlingDetector.Callback, + ZoomControl.ZoomControlListener, + View.OnClickListener, + ScrollTimerControlView.OnVisibilityChangeListener { - @Inject - lateinit var settings: AppSettings + @Inject + lateinit var settings: AppSettings - @Inject - lateinit var tapGridSettings: TapGridSettings + @Inject + lateinit var tapGridSettings: TapGridSettings - @Inject - lateinit var pageSaveHelperFactory: PageSaveHelper.Factory + @Inject + lateinit var pageSaveHelperFactory: PageSaveHelper.Factory - @Inject - lateinit var scrollTimerFactory: ScrollTimer.Factory + @Inject + lateinit var scrollTimerFactory: ScrollTimer.Factory - @Inject - lateinit var screenOrientationHelper: ScreenOrientationHelper + @Inject + lateinit var screenOrientationHelper: ScreenOrientationHelper - private val idlingDetector = IdlingDetector(TimeUnit.SECONDS.toMillis(10), this) + private val idlingDetector = IdlingDetector(TimeUnit.SECONDS.toMillis(10), this) - private val viewModel: ReaderViewModel by viewModels() + private val viewModel: ReaderViewModel by viewModels() - override val readerMode: ReaderMode? - get() = readerManager.currentMode + override val readerMode: ReaderMode? + get() = readerManager.currentMode - private lateinit var scrollTimer: ScrollTimer - private lateinit var pageSaveHelper: PageSaveHelper - private lateinit var touchHelper: TapGridDispatcher - private lateinit var controlDelegate: ReaderControlDelegate - private var gestureInsets: Insets = Insets.NONE + private lateinit var scrollTimer: ScrollTimer + private lateinit var pageSaveHelper: PageSaveHelper + private lateinit var touchHelper: TapGridDispatcher + private lateinit var controlDelegate: ReaderControlDelegate + private var gestureInsets: Insets = Insets.NONE private lateinit var readerManager: ReaderManager private val hideUiRunnable = Runnable { setUiIsVisible(false) } + // Tracks whether the foldable device is in an unfolded state (half-opened or flat) private var isFoldUnfolded: Boolean = false - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(ActivityReaderBinding.inflate(layoutInflater)) - readerManager = ReaderManager(supportFragmentManager, viewBinding.container, settings) - setDisplayHomeAsUp(isEnabled = true, showUpAsClose = false) - touchHelper = TapGridDispatcher(viewBinding.root, this) - scrollTimer = scrollTimerFactory.create(resources, this, this) - pageSaveHelper = pageSaveHelperFactory.create(this) - controlDelegate = ReaderControlDelegate(resources, settings, tapGridSettings, this) - viewBinding.zoomControl.listener = this - viewBinding.actionsView.listener = this - viewBinding.buttonTimer?.setOnClickListener(this) - idlingDetector.bindToLifecycle(this) - screenOrientationHelper.applySettings() - viewModel.isBookmarkAdded.observe(this) { viewBinding.actionsView.isBookmarkAdded = it } - scrollTimer.isActive.observe(this) { - updateScrollTimerButton() - viewBinding.actionsView.setTimerActive(it) - } - viewBinding.timerControl.onVisibilityChangeListener = this - viewBinding.timerControl.attach(scrollTimer, this) - if (resources.getBoolean(R.bool.is_tablet)) { - viewBinding.timerControl.updateLayoutParams { - topMargin = marginEnd + getThemeDimensionPixelOffset(appcompatR.attr.actionBarSize) - } - } - - viewModel.onLoadingError.observeEvent( - this, - DialogErrorObserver( - host = viewBinding.container, - fragment = null, - resolver = exceptionResolver, - onResolved = { isResolved -> - if (isResolved) { - viewModel.reload() - } else if (viewModel.content.value.pages.isEmpty()) { - dispatchNavigateUp() - } - }, - ), - ) - viewModel.onError.observeEvent( - this, - SnackbarErrorObserver( - host = viewBinding.container, - fragment = null, - resolver = exceptionResolver, - onResolved = null, - ), - ) - viewModel.readerMode.observe(this, Lifecycle.State.STARTED, this::onInitReader) - viewModel.onPageSaved.observeEvent(this, PagesSavedObserver(viewBinding.container)) - viewModel.uiState.zipWithPrevious().observe(this, this::onUiStateChanged) - combine( - viewModel.isLoading, - viewModel.content.map { it.pages.isNotEmpty() }.distinctUntilChanged(), - ::Pair, - ).flowOn(Dispatchers.Default) - .observe(this, this::onLoadingStateChanged) - viewModel.isKeepScreenOnEnabled.observe(this, this::setKeepScreenOn) - viewModel.isInfoBarTransparent.observe(this) { viewBinding.infoBar.drawBackground = !it } - viewModel.isInfoBarEnabled.observe(this, ::onReaderBarChanged) - viewModel.isBookmarkAdded.observe(this, MenuInvalidator(this)) - viewModel.onAskNsfwIncognito.observeEvent(this) { askForIncognitoMode() } - viewModel.onShowToast.observeEvent(this) { msgId -> - Snackbar.make(viewBinding.container, msgId, Snackbar.LENGTH_SHORT) - .setAnchorView(viewBinding.toolbarDocked) - .show() - } - viewModel.readerSettingsProducer.observe(this) { - viewBinding.infoBar.applyColorScheme(isBlackOnWhite = it.background.isLight(this)) - } - viewModel.isZoomControlsEnabled.observe(this) { - viewBinding.zoomControl.isVisible = it - } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(ActivityReaderBinding.inflate(layoutInflater)) + readerManager = ReaderManager(supportFragmentManager, viewBinding.container, settings) + setDisplayHomeAsUp(isEnabled = true, showUpAsClose = false) + touchHelper = TapGridDispatcher(viewBinding.root, this) + scrollTimer = scrollTimerFactory.create(resources, this, this) + pageSaveHelper = pageSaveHelperFactory.create(this) + controlDelegate = ReaderControlDelegate(resources, settings, tapGridSettings, this) + viewBinding.zoomControl.listener = this + viewBinding.actionsView.listener = this + viewBinding.buttonTimer?.setOnClickListener(this) + idlingDetector.bindToLifecycle(this) + screenOrientationHelper.applySettings() + viewModel.isBookmarkAdded.observe(this) { viewBinding.actionsView.isBookmarkAdded = it } + scrollTimer.isActive.observe(this) { + updateScrollTimerButton() + viewBinding.actionsView.setTimerActive(it) + } + viewBinding.timerControl.onVisibilityChangeListener = this + viewBinding.timerControl.attach(scrollTimer, this) + if (resources.getBoolean(R.bool.is_tablet)) { + viewBinding.timerControl.updateLayoutParams { + topMargin = marginEnd + getThemeDimensionPixelOffset(appcompatR.attr.actionBarSize) + } + } + + viewModel.onLoadingError.observeEvent( + this, + DialogErrorObserver( + host = viewBinding.container, + fragment = null, + resolver = exceptionResolver, + onResolved = { isResolved -> + if (isResolved) { + viewModel.reload() + } else if (viewModel.content.value.pages.isEmpty()) { + dispatchNavigateUp() + } + }, + ), + ) + viewModel.onError.observeEvent( + this, + SnackbarErrorObserver( + host = viewBinding.container, + fragment = null, + resolver = exceptionResolver, + onResolved = null, + ), + ) + viewModel.readerMode.observe(this, Lifecycle.State.STARTED, this::onInitReader) + viewModel.onPageSaved.observeEvent(this, PagesSavedObserver(viewBinding.container)) + viewModel.uiState.zipWithPrevious().observe(this, this::onUiStateChanged) + combine( + viewModel.isLoading, + viewModel.content.map { it.pages.isNotEmpty() }.distinctUntilChanged(), + ::Pair, + ).flowOn(Dispatchers.Default) + .observe(this, this::onLoadingStateChanged) + viewModel.isKeepScreenOnEnabled.observe(this, this::setKeepScreenOn) + viewModel.isInfoBarTransparent.observe(this) { viewBinding.infoBar.drawBackground = !it } + viewModel.isInfoBarEnabled.observe(this, ::onReaderBarChanged) + viewModel.isBookmarkAdded.observe(this, MenuInvalidator(this)) + viewModel.onAskNsfwIncognito.observeEvent(this) { askForIncognitoMode() } + viewModel.onShowToast.observeEvent(this) { msgId -> + Snackbar.make(viewBinding.container, msgId, Snackbar.LENGTH_SHORT) + .setAnchorView(viewBinding.toolbarDocked) + .show() + } + viewModel.readerSettingsProducer.observe(this) { + viewBinding.infoBar.applyColorScheme(isBlackOnWhite = it.background.isLight(this)) + } + viewModel.isZoomControlsEnabled.observe(this) { + viewBinding.zoomControl.isVisible = it + } addMenuProvider(ReaderMenuProvider(viewModel)) - // Observe foldable window layout to auto-enable double-page if configured - WindowInfoTracker.getOrCreate(this) - .windowLayoutInfo(this) - .onEach { info -> - val fold = info.displayFeatures.filterIsInstance().firstOrNull() - val unfolded = when (fold?.state) { - FoldingFeature.State.HALF_OPENED, FoldingFeature.State.FLAT -> true - else -> false - } - if (unfolded != isFoldUnfolded) { - isFoldUnfolded = unfolded - applyDoubleModeAuto() - } - } - .launchIn(lifecycleScope) + observeWindowLayout() // Apply initial double-mode considering foldable setting applyDoubleModeAuto() } - override fun getParentActivityIntent(): Intent? { - val manga = viewModel.getMangaOrNull() ?: return null - return AppRouter.detailsIntent(this, manga) - } - - override fun onUserInteraction() { - super.onUserInteraction() - if (!viewBinding.timerControl.isVisible) { - scrollTimer.onUserInteraction() - } - idlingDetector.onUserInteraction() - } - - override fun onPause() { - super.onPause() - viewModel.onPause() - } - - override fun onStop() { - super.onStop() - viewModel.onStop() - } - - override fun onProvideAssistContent(outContent: AssistContent) { - super.onProvideAssistContent(outContent) - viewModel.getMangaOrNull()?.publicUrl?.toUriOrNull()?.let { outContent.webUri = it } - } - - override fun isNsfwContent(): Flow = viewModel.isMangaNsfw - - override fun onIdle() { - viewModel.saveCurrentState(readerManager.currentReader?.getCurrentState()) - viewModel.onIdle() - } - - override fun onVisibilityChanged(v: View, visibility: Int) { - updateScrollTimerButton() - } - - override fun onZoomIn() { - readerManager.currentReader?.onZoomIn() - } - - override fun onZoomOut() { - readerManager.currentReader?.onZoomOut() - } - - override fun onClick(v: View) { - when (v.id) { - R.id.button_timer -> onScrollTimerClick(isLongClick = false) - } - } - - private fun onInitReader(mode: ReaderMode?) { - if (mode == null) { - return - } - if (readerManager.currentMode != mode) { - readerManager.replace(mode) - } - if (viewBinding.appbarTop.isVisible) { - lifecycle.postDelayed(TimeUnit.SECONDS.toMillis(1), hideUiRunnable) - } - viewBinding.actionsView.setSliderReversed(mode == ReaderMode.REVERSED) - viewBinding.timerControl.onReaderModeChanged(mode) - } - - private fun onLoadingStateChanged(value: Pair) { - val (isLoading, hasPages) = value - val showLoadingLayout = isLoading && !hasPages - if (viewBinding.layoutLoading.isVisible != showLoadingLayout) { - val transition = Fade().addTarget(viewBinding.layoutLoading) - TransitionManager.beginDelayedTransition(viewBinding.root, transition) - viewBinding.layoutLoading.isVisible = showLoadingLayout - } - if (isLoading && hasPages) { - viewBinding.toastView.show(R.string.loading_) - } else { - viewBinding.toastView.hide() - } - invalidateOptionsMenu() - } - - override fun onGridTouch(area: TapGridArea): Boolean { - return isReaderResumed() && controlDelegate.onGridTouch(area) - } - - override fun onGridLongTouch(area: TapGridArea) { - if (isReaderResumed()) { - controlDelegate.onGridLongTouch(area) - } - } - - override fun onProcessTouch(rawX: Int, rawY: Int): Boolean { - return if ( - rawX <= gestureInsets.left || - rawY <= gestureInsets.top || - rawX >= viewBinding.root.width - gestureInsets.right || - rawY >= viewBinding.root.height - gestureInsets.bottom || - viewBinding.appbarTop.hasGlobalPoint(rawX, rawY) || - viewBinding.toolbarDocked?.hasGlobalPoint(rawX, rawY) == true - ) { - false - } else { - val touchables = window.peekDecorView()?.touchables - touchables?.none { it.hasGlobalPoint(rawX, rawY) } != false - } - } - - override fun dispatchTouchEvent(ev: MotionEvent): Boolean { - touchHelper.dispatchTouchEvent(ev) - if (!viewBinding.timerControl.hasGlobalPoint(ev.rawX.toInt(), ev.rawY.toInt())) { - scrollTimer.onTouchEvent(ev) - } - return super.dispatchTouchEvent(ev) - } - - override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { - return controlDelegate.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event) - } - - override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean { - return controlDelegate.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event) - } - - override fun onChapterSelected(chapter: MangaChapter): Boolean { - viewModel.switchChapter(chapter.id, 0) - return true - } - - override fun onPageSelected(page: ReaderPage): Boolean { - lifecycleScope.launch(Dispatchers.Default) { - val pages = viewModel.content.value.pages - val index = pages.indexOfFirst { it.chapterId == page.chapterId && it.id == page.id } - if (index != -1) { - withContext(Dispatchers.Main) { - readerManager.currentReader?.switchPageTo(index, true) - } - } else { - viewModel.switchChapter(page.chapterId, page.index) - } - } - return true - } - - override fun onReaderModeChanged(mode: ReaderMode) { - viewModel.saveCurrentState(readerManager.currentReader?.getCurrentState()) - viewModel.switchMode(mode) - viewBinding.timerControl.onReaderModeChanged(mode) - } + override fun getParentActivityIntent(): Intent? { + val manga = viewModel.getMangaOrNull() ?: return null + return AppRouter.detailsIntent(this, manga) + } + + override fun onUserInteraction() { + super.onUserInteraction() + if (!viewBinding.timerControl.isVisible) { + scrollTimer.onUserInteraction() + } + idlingDetector.onUserInteraction() + } + + override fun onPause() { + super.onPause() + viewModel.onPause() + } + + override fun onStop() { + super.onStop() + viewModel.onStop() + } + + override fun onProvideAssistContent(outContent: AssistContent) { + super.onProvideAssistContent(outContent) + viewModel.getMangaOrNull()?.publicUrl?.toUriOrNull()?.let { outContent.webUri = it } + } + + override fun isNsfwContent(): Flow = viewModel.isMangaNsfw + + override fun onIdle() { + viewModel.saveCurrentState(readerManager.currentReader?.getCurrentState()) + viewModel.onIdle() + } + + override fun onVisibilityChanged(v: View, visibility: Int) { + updateScrollTimerButton() + } + + override fun onZoomIn() { + readerManager.currentReader?.onZoomIn() + } + + override fun onZoomOut() { + readerManager.currentReader?.onZoomOut() + } + + override fun onClick(v: View) { + when (v.id) { + R.id.button_timer -> onScrollTimerClick(isLongClick = false) + } + } + + private fun onInitReader(mode: ReaderMode?) { + if (mode == null) { + return + } + if (readerManager.currentMode != mode) { + readerManager.replace(mode) + } + if (viewBinding.appbarTop.isVisible) { + lifecycle.postDelayed(TimeUnit.SECONDS.toMillis(1), hideUiRunnable) + } + viewBinding.actionsView.setSliderReversed(mode == ReaderMode.REVERSED) + viewBinding.timerControl.onReaderModeChanged(mode) + } + + private fun onLoadingStateChanged(value: Pair) { + val (isLoading, hasPages) = value + val showLoadingLayout = isLoading && !hasPages + if (viewBinding.layoutLoading.isVisible != showLoadingLayout) { + val transition = Fade().addTarget(viewBinding.layoutLoading) + TransitionManager.beginDelayedTransition(viewBinding.root, transition) + viewBinding.layoutLoading.isVisible = showLoadingLayout + } + if (isLoading && hasPages) { + viewBinding.toastView.show(R.string.loading_) + } else { + viewBinding.toastView.hide() + } + invalidateOptionsMenu() + } + + override fun onGridTouch(area: TapGridArea): Boolean { + return isReaderResumed() && controlDelegate.onGridTouch(area) + } + + override fun onGridLongTouch(area: TapGridArea) { + if (isReaderResumed()) { + controlDelegate.onGridLongTouch(area) + } + } + + override fun onProcessTouch(rawX: Int, rawY: Int): Boolean { + return if ( + rawX <= gestureInsets.left || + rawY <= gestureInsets.top || + rawX >= viewBinding.root.width - gestureInsets.right || + rawY >= viewBinding.root.height - gestureInsets.bottom || + viewBinding.appbarTop.hasGlobalPoint(rawX, rawY) || + viewBinding.toolbarDocked?.hasGlobalPoint(rawX, rawY) == true + ) { + false + } else { + val touchables = window.peekDecorView()?.touchables + touchables?.none { it.hasGlobalPoint(rawX, rawY) } != false + } + } + + override fun dispatchTouchEvent(ev: MotionEvent): Boolean { + touchHelper.dispatchTouchEvent(ev) + if (!viewBinding.timerControl.hasGlobalPoint(ev.rawX.toInt(), ev.rawY.toInt())) { + scrollTimer.onTouchEvent(ev) + } + return super.dispatchTouchEvent(ev) + } + + override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { + return controlDelegate.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event) + } + + override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean { + return controlDelegate.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event) + } + + override fun onChapterSelected(chapter: MangaChapter): Boolean { + viewModel.switchChapter(chapter.id, 0) + return true + } + + override fun onPageSelected(page: ReaderPage): Boolean { + lifecycleScope.launch(Dispatchers.Default) { + val pages = viewModel.content.value.pages + val index = pages.indexOfFirst { it.chapterId == page.chapterId && it.id == page.id } + if (index != -1) { + withContext(Dispatchers.Main) { + readerManager.currentReader?.switchPageTo(index, true) + } + } else { + viewModel.switchChapter(page.chapterId, page.index) + } + } + return true + } + + override fun onReaderModeChanged(mode: ReaderMode) { + viewModel.saveCurrentState(readerManager.currentReader?.getCurrentState()) + viewModel.switchMode(mode) + viewBinding.timerControl.onReaderModeChanged(mode) + } override fun onDoubleModeChanged(isEnabled: Boolean) { // Combine manual toggle with foldable auto setting @@ -380,209 +367,227 @@ class ReaderActivity : readerManager.setDoubleReaderMode(autoEnabled) } - private fun setKeepScreenOn(isKeep: Boolean) { - if (isKeep) { - window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - } else { - window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - } - } - - private fun setUiIsVisible(isUiVisible: Boolean) { - if (viewBinding.appbarTop.isVisible != isUiVisible) { - if (isAnimationsEnabled) { - val transition = TransitionSet() - .setOrdering(TransitionSet.ORDERING_TOGETHER) - .addTransition(Slide(Gravity.TOP).addTarget(viewBinding.appbarTop)) - .addTransition(Fade().addTarget(viewBinding.infoBar)) - viewBinding.toolbarDocked?.let { - transition.addTransition(Slide(Gravity.BOTTOM).addTarget(it)) - } - TransitionManager.beginDelayedTransition(viewBinding.root, transition) - } - val isFullscreen = settings.isReaderFullscreenEnabled - viewBinding.appbarTop.isVisible = isUiVisible - viewBinding.toolbarDocked?.isVisible = isUiVisible - viewBinding.infoBar.isGone = isUiVisible || (!viewModel.isInfoBarEnabled.value) - viewBinding.infoBar.isTimeVisible = isFullscreen - updateScrollTimerButton() - systemUiController.setSystemUiVisible(isUiVisible || !isFullscreen) - viewBinding.root.requestApplyInsets() - } - } - - override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat { - gestureInsets = insets.getInsets(WindowInsetsCompat.Type.systemGestures()) - val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) - viewBinding.toolbar.updateLayoutParams { - topMargin = systemBars.top - rightMargin = systemBars.right - leftMargin = systemBars.left - } - if (viewBinding.toolbarDocked != null) { - viewBinding.actionsView.updateLayoutParams { - bottomMargin = systemBars.bottom - rightMargin = systemBars.right - leftMargin = systemBars.left - } - } - viewBinding.infoBar.updatePadding( - top = systemBars.top, - ) - val innerInsets = Insets.of( - systemBars.left, - if (viewBinding.appbarTop.isVisible) viewBinding.appbarTop.height else systemBars.top, - systemBars.right, - viewBinding.toolbarDocked?.takeIf { it.isVisible }?.height ?: systemBars.bottom, - ) - return WindowInsetsCompat.Builder(insets) - .setInsets(WindowInsetsCompat.Type.systemBars(), innerInsets) - .build() - } - - override fun switchPageBy(delta: Int) { - readerManager.currentReader?.switchPageBy(delta) - } - - override fun switchChapterBy(delta: Int) { - viewModel.switchChapterBy(delta) - } - - override fun openMenu() { - viewModel.saveCurrentState(readerManager.currentReader?.getCurrentState()) - val currentMode = readerManager.currentMode ?: return - router.showReaderConfigSheet(currentMode) - } - - override fun scrollBy(delta: Int, smooth: Boolean): Boolean { - return readerManager.currentReader?.scrollBy(delta, smooth) == true - } - - override fun toggleUiVisibility() { - setUiIsVisible(!viewBinding.appbarTop.isVisible) - } - - override fun isReaderResumed(): Boolean { - val reader = readerManager.currentReader ?: return false - return reader.isResumed && supportFragmentManager.fragments.lastOrNull() === reader - } - - override fun onBookmarkClick() { - viewModel.toggleBookmark() - } - - override fun onSavePageClick() { - viewModel.saveCurrentPage(pageSaveHelper) - } - - override fun onScrollTimerClick(isLongClick: Boolean) { - if (isLongClick) { - scrollTimer.setActive(!scrollTimer.isActive.value) - } else { - viewBinding.timerControl.showOrHide() - } - } - - override fun toggleScreenOrientation() { - if (screenOrientationHelper.toggleScreenOrientation()) { - Snackbar.make( - viewBinding.container, - if (screenOrientationHelper.isLocked) { - R.string.screen_rotation_locked - } else { - R.string.screen_rotation_unlocked - }, - Snackbar.LENGTH_SHORT, - ).setAnchorView(viewBinding.toolbarDocked) - .show() - } - } - - override fun switchPageTo(index: Int) { - val pages = viewModel.getCurrentChapterPages() - val page = pages?.getOrNull(index) ?: return - val chapterId = viewModel.getCurrentState()?.chapterId ?: return - onPageSelected(ReaderPage(page, index, chapterId)) - } - - private fun onReaderBarChanged(isBarEnabled: Boolean) { - viewBinding.infoBar.isVisible = isBarEnabled && viewBinding.appbarTop.isGone - } - - private fun onUiStateChanged(pair: Pair) { - val (previous: ReaderUiState?, uiState: ReaderUiState?) = pair - title = uiState?.mangaName ?: getString(R.string.loading_) - viewBinding.infoBar.update(uiState) - if (uiState == null) { - supportActionBar?.subtitle = null - viewBinding.actionsView.setSliderValue(0, 1) - viewBinding.actionsView.isSliderEnabled = false - return - } - val chapterTitle = uiState.getChapterTitle(resources) - supportActionBar?.subtitle = when { - uiState.incognito -> getString(R.string.incognito_mode) - else -> chapterTitle - } - if ( - settings.isReaderChapterToastEnabled && - chapterTitle != previous?.getChapterTitle(resources) && - chapterTitle.isNotEmpty() - ) { - viewBinding.toastView.showTemporary(chapterTitle, TOAST_DURATION) - } - if (uiState.isSliderAvailable()) { - viewBinding.actionsView.setSliderValue( - value = uiState.currentPage, - max = uiState.totalPages - 1, - ) - } else { - viewBinding.actionsView.setSliderValue(0, 1) - } - viewBinding.actionsView.isSliderEnabled = uiState.isSliderAvailable() - viewBinding.actionsView.isNextEnabled = uiState.hasNextChapter() - viewBinding.actionsView.isPrevEnabled = uiState.hasPreviousChapter() - } - - private fun updateScrollTimerButton() { - val button = viewBinding.buttonTimer ?: return - val isButtonVisible = scrollTimer.isActive.value - && settings.isReaderAutoscrollFabVisible - && !viewBinding.appbarTop.isVisible - && !viewBinding.timerControl.isVisible - if (button.isVisible != isButtonVisible) { - val transition = Fade().addTarget(button) - TransitionManager.beginDelayedTransition(viewBinding.root, transition) - button.isVisible = isButtonVisible - } - } - - private fun askForIncognitoMode() { - buildAlertDialog(this, isCentered = true) { - var dontAskAgain = false - val listener = DialogInterface.OnClickListener { _, which -> - if (which == DialogInterface.BUTTON_NEUTRAL) { - finishAfterTransition() - } else { - viewModel.setIncognitoMode(which == DialogInterface.BUTTON_POSITIVE, dontAskAgain) - } - } - setCheckbox(R.string.dont_ask_again, dontAskAgain) { _, isChecked -> - dontAskAgain = isChecked - } - setIcon(R.drawable.ic_incognito) - setTitle(R.string.incognito_mode) - setMessage(R.string.incognito_mode_hint_nsfw) - setPositiveButton(R.string.incognito, listener) - setNegativeButton(R.string.disable, listener) - setNeutralButton(android.R.string.cancel, listener) - setOnCancelListener { finishAfterTransition() } - setCancelable(true) - }.show() - } - - companion object { - - private const val TOAST_DURATION = 2000L - } + private fun setKeepScreenOn(isKeep: Boolean) { + if (isKeep) { + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + } else { + window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + } + } + + private fun setUiIsVisible(isUiVisible: Boolean) { + if (viewBinding.appbarTop.isVisible != isUiVisible) { + if (isAnimationsEnabled) { + val transition = TransitionSet() + .setOrdering(TransitionSet.ORDERING_TOGETHER) + .addTransition(Slide(Gravity.TOP).addTarget(viewBinding.appbarTop)) + .addTransition(Fade().addTarget(viewBinding.infoBar)) + viewBinding.toolbarDocked?.let { + transition.addTransition(Slide(Gravity.BOTTOM).addTarget(it)) + } + TransitionManager.beginDelayedTransition(viewBinding.root, transition) + } + val isFullscreen = settings.isReaderFullscreenEnabled + viewBinding.appbarTop.isVisible = isUiVisible + viewBinding.toolbarDocked?.isVisible = isUiVisible + viewBinding.infoBar.isGone = isUiVisible || (!viewModel.isInfoBarEnabled.value) + viewBinding.infoBar.isTimeVisible = isFullscreen + updateScrollTimerButton() + systemUiController.setSystemUiVisible(isUiVisible || !isFullscreen) + viewBinding.root.requestApplyInsets() + } + } + + override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat { + gestureInsets = insets.getInsets(WindowInsetsCompat.Type.systemGestures()) + val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) + viewBinding.toolbar.updateLayoutParams { + topMargin = systemBars.top + rightMargin = systemBars.right + leftMargin = systemBars.left + } + if (viewBinding.toolbarDocked != null) { + viewBinding.actionsView.updateLayoutParams { + bottomMargin = systemBars.bottom + rightMargin = systemBars.right + leftMargin = systemBars.left + } + } + viewBinding.infoBar.updatePadding( + top = systemBars.top, + ) + val innerInsets = Insets.of( + systemBars.left, + if (viewBinding.appbarTop.isVisible) viewBinding.appbarTop.height else systemBars.top, + systemBars.right, + viewBinding.toolbarDocked?.takeIf { it.isVisible }?.height ?: systemBars.bottom, + ) + return WindowInsetsCompat.Builder(insets) + .setInsets(WindowInsetsCompat.Type.systemBars(), innerInsets) + .build() + } + + override fun switchPageBy(delta: Int) { + readerManager.currentReader?.switchPageBy(delta) + } + + override fun switchChapterBy(delta: Int) { + viewModel.switchChapterBy(delta) + } + + override fun openMenu() { + viewModel.saveCurrentState(readerManager.currentReader?.getCurrentState()) + val currentMode = readerManager.currentMode ?: return + router.showReaderConfigSheet(currentMode) + } + + override fun scrollBy(delta: Int, smooth: Boolean): Boolean { + return readerManager.currentReader?.scrollBy(delta, smooth) == true + } + + override fun toggleUiVisibility() { + setUiIsVisible(!viewBinding.appbarTop.isVisible) + } + + override fun isReaderResumed(): Boolean { + val reader = readerManager.currentReader ?: return false + return reader.isResumed && supportFragmentManager.fragments.lastOrNull() === reader + } + + override fun onBookmarkClick() { + viewModel.toggleBookmark() + } + + override fun onSavePageClick() { + viewModel.saveCurrentPage(pageSaveHelper) + } + + override fun onScrollTimerClick(isLongClick: Boolean) { + if (isLongClick) { + scrollTimer.setActive(!scrollTimer.isActive.value) + } else { + viewBinding.timerControl.showOrHide() + } + } + + override fun toggleScreenOrientation() { + if (screenOrientationHelper.toggleScreenOrientation()) { + Snackbar.make( + viewBinding.container, + if (screenOrientationHelper.isLocked) { + R.string.screen_rotation_locked + } else { + R.string.screen_rotation_unlocked + }, + Snackbar.LENGTH_SHORT, + ).setAnchorView(viewBinding.toolbarDocked) + .show() + } + } + + override fun switchPageTo(index: Int) { + val pages = viewModel.getCurrentChapterPages() + val page = pages?.getOrNull(index) ?: return + val chapterId = viewModel.getCurrentState()?.chapterId ?: return + onPageSelected(ReaderPage(page, index, chapterId)) + } + + private fun onReaderBarChanged(isBarEnabled: Boolean) { + viewBinding.infoBar.isVisible = isBarEnabled && viewBinding.appbarTop.isGone + } + + private fun onUiStateChanged(pair: Pair) { + val (previous: ReaderUiState?, uiState: ReaderUiState?) = pair + title = uiState?.mangaName ?: getString(R.string.loading_) + viewBinding.infoBar.update(uiState) + if (uiState == null) { + supportActionBar?.subtitle = null + viewBinding.actionsView.setSliderValue(0, 1) + viewBinding.actionsView.isSliderEnabled = false + return + } + val chapterTitle = uiState.getChapterTitle(resources) + supportActionBar?.subtitle = when { + uiState.incognito -> getString(R.string.incognito_mode) + else -> chapterTitle + } + if ( + settings.isReaderChapterToastEnabled && + chapterTitle != previous?.getChapterTitle(resources) && + chapterTitle.isNotEmpty() + ) { + viewBinding.toastView.showTemporary(chapterTitle, TOAST_DURATION) + } + if (uiState.isSliderAvailable()) { + viewBinding.actionsView.setSliderValue( + value = uiState.currentPage, + max = uiState.totalPages - 1, + ) + } else { + viewBinding.actionsView.setSliderValue(0, 1) + } + viewBinding.actionsView.isSliderEnabled = uiState.isSliderAvailable() + viewBinding.actionsView.isNextEnabled = uiState.hasNextChapter() + viewBinding.actionsView.isPrevEnabled = uiState.hasPreviousChapter() + } + + private fun updateScrollTimerButton() { + val button = viewBinding.buttonTimer ?: return + val isButtonVisible = scrollTimer.isActive.value + && settings.isReaderAutoscrollFabVisible + && !viewBinding.appbarTop.isVisible + && !viewBinding.timerControl.isVisible + if (button.isVisible != isButtonVisible) { + val transition = Fade().addTarget(button) + TransitionManager.beginDelayedTransition(viewBinding.root, transition) + button.isVisible = isButtonVisible + } + } + + // Observe foldable window layout to auto-enable double-page if configured + private fun observeWindowLayout() { + WindowInfoTracker.getOrCreate(this) + .windowLayoutInfo(this) + .onEach { info -> + val fold = info.displayFeatures.filterIsInstance().firstOrNull() + val unfolded = when (fold?.state) { + FoldingFeature.State.HALF_OPENED, FoldingFeature.State.FLAT -> true + else -> false + } + if (unfolded != isFoldUnfolded) { + isFoldUnfolded = unfolded + applyDoubleModeAuto() + } + } + .launchIn(lifecycleScope) + } + + private fun askForIncognitoMode() { + buildAlertDialog(this, isCentered = true) { + var dontAskAgain = false + val listener = DialogInterface.OnClickListener { _, which -> + if (which == DialogInterface.BUTTON_NEUTRAL) { + finishAfterTransition() + } else { + viewModel.setIncognitoMode(which == DialogInterface.BUTTON_POSITIVE, dontAskAgain) + } + } + setCheckbox(R.string.dont_ask_again, dontAskAgain) { _, isChecked -> + dontAskAgain = isChecked + } + setIcon(R.drawable.ic_incognito) + setTitle(R.string.incognito_mode) + setMessage(R.string.incognito_mode_hint_nsfw) + setPositiveButton(R.string.incognito, listener) + setNegativeButton(R.string.disable, listener) + setNeutralButton(android.R.string.cancel, listener) + setOnCancelListener { finishAfterTransition() } + setCancelable(true) + }.show() + } + + companion object { + + private const val TOAST_DURATION = 2000L + } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/config/ReaderConfigSheet.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/config/ReaderConfigSheet.kt index 968611913..68cab39ff 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/config/ReaderConfigSheet.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/config/ReaderConfigSheet.kt @@ -38,226 +38,226 @@ import javax.inject.Inject @AndroidEntryPoint class ReaderConfigSheet : - BaseAdaptiveSheet(), - View.OnClickListener, - MaterialButtonToggleGroup.OnButtonCheckedListener, - CompoundButton.OnCheckedChangeListener, - Slider.OnChangeListener { - - private val viewModel by activityViewModels() - - @Inject - lateinit var orientationHelper: ScreenOrientationHelper - - @Inject - lateinit var mangaRepositoryFactory: MangaRepository.Factory - - @Inject - lateinit var pageLoader: PageLoader - - private lateinit var mode: ReaderMode - private lateinit var imageServerDelegate: ImageServerDelegate - - @Inject - lateinit var settings: AppSettings - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - mode = arguments?.getInt(AppRouter.KEY_READER_MODE) - ?.let { ReaderMode.valueOf(it) } - ?: ReaderMode.STANDARD - imageServerDelegate = ImageServerDelegate( - mangaRepositoryFactory = mangaRepositoryFactory, - mangaSource = viewModel.getMangaOrNull()?.source, - ) - } - - override fun onCreateViewBinding( - inflater: LayoutInflater, - container: ViewGroup?, - ): SheetReaderConfigBinding { - return SheetReaderConfigBinding.inflate(inflater, container, false) - } - - override fun onViewBindingCreated( - binding: SheetReaderConfigBinding, - savedInstanceState: Bundle?, - ) { - super.onViewBindingCreated(binding, savedInstanceState) - observeScreenOrientation() - binding.buttonStandard.isChecked = mode == ReaderMode.STANDARD - binding.buttonReversed.isChecked = mode == ReaderMode.REVERSED - binding.buttonWebtoon.isChecked = mode == ReaderMode.WEBTOON - binding.buttonVertical.isChecked = mode == ReaderMode.VERTICAL - binding.switchDoubleReader.isChecked = settings.isReaderDoubleOnLandscape - binding.switchDoubleReader.isEnabled = mode == ReaderMode.STANDARD || mode == ReaderMode.REVERSED - binding.switchDoubleFoldable.isChecked = settings.isReaderDoubleOnFoldable - binding.switchDoubleFoldable.isEnabled = binding.switchDoubleReader.isEnabled - binding.sliderDoubleSensitivity.setValueRounded(settings.readerDoublePagesSensitivity * 100f) - binding.sliderDoubleSensitivity.setLabelFormatter(IntPercentLabelFormatter(binding.root.context)) - binding.adjustSensitivitySlider(withAnimation = false) - - binding.checkableGroup.addOnButtonCheckedListener(this) - binding.buttonSavePage.setOnClickListener(this) - binding.buttonScreenRotate.setOnClickListener(this) - binding.buttonSettings.setOnClickListener(this) - binding.buttonImageServer.setOnClickListener(this) - binding.buttonColorFilter.setOnClickListener(this) - binding.buttonScrollTimer.setOnClickListener(this) - binding.buttonBookmark.setOnClickListener(this) - binding.switchDoubleReader.setOnCheckedChangeListener(this) - binding.switchDoubleFoldable.setOnCheckedChangeListener(this) - binding.sliderDoubleSensitivity.addOnChangeListener(this) - - viewModel.isBookmarkAdded.observe(viewLifecycleOwner) { - binding.buttonBookmark.setText(if (it) R.string.bookmark_remove else R.string.bookmark_add) - binding.buttonBookmark.setCompoundDrawablesRelativeWithIntrinsicBounds( - if (it) R.drawable.ic_bookmark_checked else R.drawable.ic_bookmark, 0, 0, 0, - ) - } - - viewLifecycleScope.launch { - val isAvailable = imageServerDelegate.isAvailable() - if (isAvailable) { - bindImageServerTitle() - } - binding.buttonImageServer.isVisible = isAvailable - } - } - - override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat { - val typeMask = WindowInsetsCompat.Type.systemBars() - viewBinding?.scrollView?.updatePadding( - bottom = insets.getInsets(typeMask).bottom, - ) - return insets.consume(v, typeMask, bottom = true) - } - - override fun onClick(v: View) { - when (v.id) { - R.id.button_settings -> { - router.openReaderSettings() - dismissAllowingStateLoss() - } - - R.id.button_scroll_timer -> { - findParentCallback(Callback::class.java)?.onScrollTimerClick(false) ?: return - dismissAllowingStateLoss() - } - - R.id.button_save_page -> { - findParentCallback(Callback::class.java)?.onSavePageClick() ?: return - dismissAllowingStateLoss() - } - - R.id.button_screen_rotate -> { - orientationHelper.isLandscape = !orientationHelper.isLandscape - } - - R.id.button_bookmark -> { - viewModel.toggleBookmark() - } - - R.id.button_color_filter -> { - val page = viewModel.getCurrentPage() ?: return - val manga = viewModel.getMangaOrNull() ?: return - router.openColorFilterConfig(manga, page) - } - - R.id.button_image_server -> viewLifecycleScope.launch { - if (imageServerDelegate.showDialog(v.context)) { - bindImageServerTitle() - pageLoader.invalidate(clearCache = true) - viewModel.switchChapterBy(0) - } - } - } - } - - override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) { - when (buttonView.id) { - R.id.switch_screen_lock_rotation -> { - orientationHelper.isLocked = isChecked - } - - R.id.switch_double_reader -> { - settings.isReaderDoubleOnLandscape = isChecked - viewBinding?.adjustSensitivitySlider(withAnimation = true) - findParentCallback(Callback::class.java)?.onDoubleModeChanged(isChecked) - } - - R.id.switch_double_foldable -> { - settings.isReaderDoubleOnFoldable = isChecked - // Re-evaluate double-page considering foldable state and current manual toggle - findParentCallback(Callback::class.java)?.onDoubleModeChanged(settings.isReaderDoubleOnLandscape) - } - } - } - - override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) { - settings.readerDoublePagesSensitivity = value / 100f - } - - override fun onButtonChecked( - group: MaterialButtonToggleGroup?, - checkedId: Int, - isChecked: Boolean, - ) { - if (!isChecked) { - return - } - val newMode = when (checkedId) { - R.id.button_standard -> ReaderMode.STANDARD - R.id.button_webtoon -> ReaderMode.WEBTOON - R.id.button_reversed -> ReaderMode.REVERSED - R.id.button_vertical -> ReaderMode.VERTICAL - else -> return - } - viewBinding?.run { - switchDoubleReader.isEnabled = newMode == ReaderMode.STANDARD || newMode == ReaderMode.REVERSED - switchDoubleFoldable.isEnabled = switchDoubleReader.isEnabled - adjustSensitivitySlider(withAnimation = true) - } - if (newMode == mode) { - return - } - findParentCallback(Callback::class.java)?.onReaderModeChanged(newMode) ?: return - mode = newMode - } - - private fun observeScreenOrientation() { - orientationHelper.observeAutoOrientation() - .onEach { - with(requireViewBinding()) { - buttonScreenRotate.isGone = it - switchScreenLockRotation.isVisible = it - updateOrientationLockSwitch() - } - }.launchIn(viewLifecycleScope) - } - - private fun updateOrientationLockSwitch() { - val switch = viewBinding?.switchScreenLockRotation ?: return - switch.setOnCheckedChangeListener(null) - switch.isChecked = orientationHelper.isLocked - switch.setOnCheckedChangeListener(this) - } - - private suspend fun bindImageServerTitle() { - viewBinding?.buttonImageServer?.text = getString( - R.string.inline_preference_pattern, - getString(R.string.image_server), - imageServerDelegate.getValue() ?: getString(R.string.automatic), - ) - } + BaseAdaptiveSheet(), + View.OnClickListener, + MaterialButtonToggleGroup.OnButtonCheckedListener, + CompoundButton.OnCheckedChangeListener, + Slider.OnChangeListener { + + private val viewModel by activityViewModels() + + @Inject + lateinit var orientationHelper: ScreenOrientationHelper + + @Inject + lateinit var mangaRepositoryFactory: MangaRepository.Factory + + @Inject + lateinit var pageLoader: PageLoader + + private lateinit var mode: ReaderMode + private lateinit var imageServerDelegate: ImageServerDelegate + + @Inject + lateinit var settings: AppSettings + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + mode = arguments?.getInt(AppRouter.KEY_READER_MODE) + ?.let { ReaderMode.valueOf(it) } + ?: ReaderMode.STANDARD + imageServerDelegate = ImageServerDelegate( + mangaRepositoryFactory = mangaRepositoryFactory, + mangaSource = viewModel.getMangaOrNull()?.source, + ) + } + + override fun onCreateViewBinding( + inflater: LayoutInflater, + container: ViewGroup?, + ): SheetReaderConfigBinding { + return SheetReaderConfigBinding.inflate(inflater, container, false) + } + + override fun onViewBindingCreated( + binding: SheetReaderConfigBinding, + savedInstanceState: Bundle?, + ) { + super.onViewBindingCreated(binding, savedInstanceState) + observeScreenOrientation() + binding.buttonStandard.isChecked = mode == ReaderMode.STANDARD + binding.buttonReversed.isChecked = mode == ReaderMode.REVERSED + binding.buttonWebtoon.isChecked = mode == ReaderMode.WEBTOON + binding.buttonVertical.isChecked = mode == ReaderMode.VERTICAL + binding.switchDoubleReader.isChecked = settings.isReaderDoubleOnLandscape + binding.switchDoubleReader.isEnabled = mode == ReaderMode.STANDARD || mode == ReaderMode.REVERSED + binding.switchDoubleFoldable.isChecked = settings.isReaderDoubleOnFoldable + binding.switchDoubleFoldable.isEnabled = binding.switchDoubleReader.isEnabled + binding.sliderDoubleSensitivity.setValueRounded(settings.readerDoublePagesSensitivity * 100f) + binding.sliderDoubleSensitivity.setLabelFormatter(IntPercentLabelFormatter(binding.root.context)) + binding.adjustSensitivitySlider(withAnimation = false) + + binding.checkableGroup.addOnButtonCheckedListener(this) + binding.buttonSavePage.setOnClickListener(this) + binding.buttonScreenRotate.setOnClickListener(this) + binding.buttonSettings.setOnClickListener(this) + binding.buttonImageServer.setOnClickListener(this) + binding.buttonColorFilter.setOnClickListener(this) + binding.buttonScrollTimer.setOnClickListener(this) + binding.buttonBookmark.setOnClickListener(this) + binding.switchDoubleReader.setOnCheckedChangeListener(this) + binding.switchDoubleFoldable.setOnCheckedChangeListener(this) + binding.sliderDoubleSensitivity.addOnChangeListener(this) + + viewModel.isBookmarkAdded.observe(viewLifecycleOwner) { + binding.buttonBookmark.setText(if (it) R.string.bookmark_remove else R.string.bookmark_add) + binding.buttonBookmark.setCompoundDrawablesRelativeWithIntrinsicBounds( + if (it) R.drawable.ic_bookmark_checked else R.drawable.ic_bookmark, 0, 0, 0, + ) + } + + viewLifecycleScope.launch { + val isAvailable = imageServerDelegate.isAvailable() + if (isAvailable) { + bindImageServerTitle() + } + binding.buttonImageServer.isVisible = isAvailable + } + } + + override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat { + val typeMask = WindowInsetsCompat.Type.systemBars() + viewBinding?.scrollView?.updatePadding( + bottom = insets.getInsets(typeMask).bottom, + ) + return insets.consume(v, typeMask, bottom = true) + } + + override fun onClick(v: View) { + when (v.id) { + R.id.button_settings -> { + router.openReaderSettings() + dismissAllowingStateLoss() + } + + R.id.button_scroll_timer -> { + findParentCallback(Callback::class.java)?.onScrollTimerClick(false) ?: return + dismissAllowingStateLoss() + } + + R.id.button_save_page -> { + findParentCallback(Callback::class.java)?.onSavePageClick() ?: return + dismissAllowingStateLoss() + } + + R.id.button_screen_rotate -> { + orientationHelper.isLandscape = !orientationHelper.isLandscape + } + + R.id.button_bookmark -> { + viewModel.toggleBookmark() + } + + R.id.button_color_filter -> { + val page = viewModel.getCurrentPage() ?: return + val manga = viewModel.getMangaOrNull() ?: return + router.openColorFilterConfig(manga, page) + } + + R.id.button_image_server -> viewLifecycleScope.launch { + if (imageServerDelegate.showDialog(v.context)) { + bindImageServerTitle() + pageLoader.invalidate(clearCache = true) + viewModel.switchChapterBy(0) + } + } + } + } + + override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) { + when (buttonView.id) { + R.id.switch_screen_lock_rotation -> { + orientationHelper.isLocked = isChecked + } + + R.id.switch_double_reader -> { + settings.isReaderDoubleOnLandscape = isChecked + viewBinding?.adjustSensitivitySlider(withAnimation = true) + findParentCallback(Callback::class.java)?.onDoubleModeChanged(isChecked) + } + + R.id.switch_double_foldable -> { + settings.isReaderDoubleOnFoldable = isChecked + // Re-evaluate double-page considering foldable state and current manual toggle + findParentCallback(Callback::class.java)?.onDoubleModeChanged(settings.isReaderDoubleOnLandscape) + } + } + } + + override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) { + settings.readerDoublePagesSensitivity = value / 100f + } + + override fun onButtonChecked( + group: MaterialButtonToggleGroup?, + checkedId: Int, + isChecked: Boolean, + ) { + if (!isChecked) { + return + } + val newMode = when (checkedId) { + R.id.button_standard -> ReaderMode.STANDARD + R.id.button_webtoon -> ReaderMode.WEBTOON + R.id.button_reversed -> ReaderMode.REVERSED + R.id.button_vertical -> ReaderMode.VERTICAL + else -> return + } + viewBinding?.run { + switchDoubleReader.isEnabled = newMode == ReaderMode.STANDARD || newMode == ReaderMode.REVERSED + switchDoubleFoldable.isEnabled = switchDoubleReader.isEnabled + adjustSensitivitySlider(withAnimation = true) + } + if (newMode == mode) { + return + } + findParentCallback(Callback::class.java)?.onReaderModeChanged(newMode) ?: return + mode = newMode + } + + private fun observeScreenOrientation() { + orientationHelper.observeAutoOrientation() + .onEach { + with(requireViewBinding()) { + buttonScreenRotate.isGone = it + switchScreenLockRotation.isVisible = it + updateOrientationLockSwitch() + } + }.launchIn(viewLifecycleScope) + } + + private fun updateOrientationLockSwitch() { + val switch = viewBinding?.switchScreenLockRotation ?: return + switch.setOnCheckedChangeListener(null) + switch.isChecked = orientationHelper.isLocked + switch.setOnCheckedChangeListener(this) + } + + private suspend fun bindImageServerTitle() { + viewBinding?.buttonImageServer?.text = getString( + R.string.inline_preference_pattern, + getString(R.string.image_server), + imageServerDelegate.getValue() ?: getString(R.string.automatic), + ) + } private fun SheetReaderConfigBinding.adjustSensitivitySlider(withAnimation: Boolean) { val isSubOptionsVisible = switchDoubleReader.isEnabled && switchDoubleReader.isChecked val needTransition = withAnimation && ( (isSubOptionsVisible != sliderDoubleSensitivity.isVisible) || - (isSubOptionsVisible != textDoubleSensitivity.isVisible) || - (isSubOptionsVisible != switchDoubleFoldable.isVisible) - ) + (isSubOptionsVisible != textDoubleSensitivity.isVisible) || + (isSubOptionsVisible != switchDoubleFoldable.isVisible) + ) if (needTransition) { TransitionManager.beginDelayedTransition(layoutMain) } @@ -266,16 +266,16 @@ class ReaderConfigSheet : switchDoubleFoldable.isVisible = isSubOptionsVisible } - interface Callback { + interface Callback { - fun onReaderModeChanged(mode: ReaderMode) + fun onReaderModeChanged(mode: ReaderMode) - fun onDoubleModeChanged(isEnabled: Boolean) + fun onDoubleModeChanged(isEnabled: Boolean) - fun onSavePageClick() + fun onSavePageClick() - fun onScrollTimerClick(isLongClick: Boolean) + fun onScrollTimerClick(isLongClick: Boolean) - fun onBookmarkClick() - } + fun onBookmarkClick() + } }