diff --git a/app/src/main/java/eu/kanade/presentation/reader/BottomReaderBar.kt b/app/src/main/java/eu/kanade/presentation/reader/BottomReaderBar.kt new file mode 100644 index 000000000..e88692ff0 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/reader/BottomReaderBar.kt @@ -0,0 +1,76 @@ +package eu.kanade.presentation.reader + +import androidx.compose.foundation.background +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Settings +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.surfaceColorAtElevation +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.ui.reader.setting.OrientationType +import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType + +@Composable +fun BottomReaderBar( + readingMode: ReadingModeType, + onClickReadingMode: () -> Unit, + orientationMode: OrientationType, + onClickOrientationMode: () -> Unit, + cropEnabled: Boolean, + onClickCropBorder: () -> Unit, + onClickSettings: () -> Unit, +) { + // Match with toolbar background color set in ReaderActivity + val backgroundColor = MaterialTheme.colorScheme + .surfaceColorAtElevation(3.dp) + .copy(alpha = if (isSystemInDarkTheme()) 0.9f else 0.95f) + + Row( + modifier = Modifier + .fillMaxWidth() + .background(backgroundColor) + .padding(8.dp), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically, + ) { + IconButton(onClick = onClickReadingMode) { + Icon( + painter = painterResource(readingMode.iconRes), + contentDescription = stringResource(R.string.viewer), + ) + } + + IconButton(onClick = onClickCropBorder) { + Icon( + painter = painterResource(if (cropEnabled) R.drawable.ic_crop_24dp else R.drawable.ic_crop_off_24dp), + contentDescription = stringResource(R.string.pref_crop_borders), + ) + } + + IconButton(onClick = onClickOrientationMode) { + Icon( + painter = painterResource(orientationMode.iconRes), + contentDescription = stringResource(R.string.pref_rotation_type), + ) + } + + IconButton(onClick = onClickSettings) { + Icon( + imageVector = Icons.Outlined.Settings, + contentDescription = stringResource(R.string.action_settings), + ) + } + } +} diff --git a/app/src/main/java/eu/kanade/presentation/reader/ChapterNavigator.kt b/app/src/main/java/eu/kanade/presentation/reader/ChapterNavigator.kt index c7806af43..6432b8e1b 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/ChapterNavigator.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/ChapterNavigator.kt @@ -53,6 +53,15 @@ fun ChapterNavigator( val layoutDirection = if (isRtl) LayoutDirection.Rtl else LayoutDirection.Ltr val haptic = LocalHapticFeedback.current + // Match with toolbar background color set in ReaderActivity + val backgroundColor = MaterialTheme.colorScheme + .surfaceColorAtElevation(3.dp) + .copy(alpha = if (isSystemInDarkTheme()) 0.9f else 0.95f) + val buttonColor = IconButtonDefaults.filledIconButtonColors( + containerColor = backgroundColor, + disabledContainerColor = backgroundColor, + ) + // We explicitly handle direction based on the reader viewer rather than the system direction CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) { Row( @@ -61,14 +70,6 @@ fun ChapterNavigator( .padding(horizontal = horizontalPadding), verticalAlignment = Alignment.CenterVertically, ) { - // Match with toolbar background color set in ReaderActivity - val backgroundColor = MaterialTheme.colorScheme - .surfaceColorAtElevation(3.dp) - .copy(alpha = if (isSystemInDarkTheme()) 0.9f else 0.95f) - val buttonColor = IconButtonDefaults.filledIconButtonColors( - containerColor = backgroundColor, - disabledContainerColor = backgroundColor, - ) FilledIconButton( enabled = if (isRtl) enabledNext else enabledPrevious, onClick = if (isRtl) onNextChapter else onPreviousChapter, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index 5c21fb6fb..1e8971057 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -25,6 +25,7 @@ import android.view.animation.AnimationUtils import android.widget.Toast import androidx.activity.viewModels import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.material3.AlertDialog import androidx.compose.material3.CircularProgressIndicator @@ -49,6 +50,7 @@ import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.transition.platform.MaterialContainerTransform import dev.chrisbanes.insetter.applyInsetter import eu.kanade.domain.base.BasePreferences +import eu.kanade.presentation.reader.BottomReaderBar import eu.kanade.presentation.reader.ChapterNavigator import eu.kanade.presentation.reader.OrientationModeSelectDialog import eu.kanade.presentation.reader.PageIndicatorText @@ -80,9 +82,7 @@ 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.copy import eu.kanade.tachiyomi.util.view.setComposeContent -import eu.kanade.tachiyomi.util.view.setTooltip import eu.kanade.tachiyomi.widget.listener.SimpleAnimationListener import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.drop @@ -95,12 +95,12 @@ import kotlinx.coroutines.flow.sample import kotlinx.coroutines.launch import logcat.LogPriority import tachiyomi.core.Constants -import tachiyomi.core.preference.toggle import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchNonCancellable import tachiyomi.core.util.lang.withUIContext import tachiyomi.core.util.system.logcat import tachiyomi.domain.manga.model.Manga +import tachiyomi.presentation.core.util.collectAsState import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import kotlin.math.abs @@ -434,8 +434,6 @@ class ReaderActivity : BaseActivity() { if (!readerPreferences.showReadingMode().get()) { menuToggleToast = toast(stringRes) } - - updateCropBordersShortcut() }, ) } @@ -461,36 +459,61 @@ class ReaderActivity : BaseActivity() { } } - // Init listeners on bottom menu - binding.readerNav.setComposeContent { + binding.readerMenuBottom.setComposeContent { val state by viewModel.state.collectAsState() if (state.viewer == null) return@setComposeContent val isRtl = state.viewer is R2LPagerViewer - ChapterNavigator( - isRtl = isRtl, - onNextChapter = ::loadNextChapter, - enabledNext = state.viewerChapters?.nextChapter != null, - onPreviousChapter = ::loadPreviousChapter, - enabledPrevious = state.viewerChapters?.prevChapter != null, - currentPage = state.currentPage, - totalPages = state.totalPages, - onSliderValueChange = { - isScrollingThroughPages = true - moveToPageIndex(it) - }, - ) - } + val cropBorderPaged by readerPreferences.cropBorders().collectAsState() + val cropBorderWebtoon by readerPreferences.cropBordersWebtoon().collectAsState() + val isPagerType = ReadingModeType.isPagerType(viewModel.getMangaReadingMode()) + val cropEnabled = if (isPagerType) cropBorderPaged else cropBorderWebtoon - initBottomShortcuts() + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + ChapterNavigator( + isRtl = isRtl, + onNextChapter = ::loadNextChapter, + enabledNext = state.viewerChapters?.nextChapter != null, + onPreviousChapter = ::loadPreviousChapter, + enabledPrevious = state.viewerChapters?.prevChapter != null, + currentPage = state.currentPage, + totalPages = state.totalPages, + onSliderValueChange = { + isScrollingThroughPages = true + moveToPageIndex(it) + }, + ) + + BottomReaderBar( + readingMode = ReadingModeType.fromPreference(viewModel.getMangaReadingMode(resolveDefault = false)), + onClickReadingMode = viewModel::openReadingModeSelectDialog, + orientationMode = OrientationType.fromPreference(viewModel.getMangaOrientationType(resolveDefault = false)), + onClickOrientationMode = viewModel::openOrientationModeSelectDialog, + cropEnabled = cropEnabled, + onClickCropBorder = { + val enabled = viewModel.toggleCropBorders() + + menuToggleToast?.cancel() + menuToggleToast = toast( + if (enabled) { + R.string.on + } else { + R.string.off + }, + ) + }, + onClickSettings = viewModel::openSettingsDialog, + ) + } + } 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 } - binding.toolbarBottom.background = toolbarBackground.copy(this@ReaderActivity) - val toolbarColor = ColorUtils.setAlphaComponent( toolbarBackground.resolvedTintColor, toolbarBackground.alpha, @@ -504,87 +527,6 @@ class ReaderActivity : BaseActivity() { setMenuVisibility(viewModel.state.value.menuVisible) } - private fun initBottomShortcuts() { - // Reading mode - with(binding.actionReadingMode) { - setTooltip(R.string.viewer) - - setOnClickListener { - viewModel.openReadingModeSelectDialog() - } - } - - // Crop borders - with(binding.actionCropBorders) { - setTooltip(R.string.pref_crop_borders) - - setOnClickListener { - val isPagerType = ReadingModeType.isPagerType(viewModel.getMangaReadingMode()) - val enabled = if (isPagerType) { - readerPreferences.cropBorders().toggle() - } else { - readerPreferences.cropBordersWebtoon().toggle() - } - - menuToggleToast?.cancel() - menuToggleToast = toast( - if (enabled) { - R.string.on - } else { - R.string.off - }, - ) - } - } - updateCropBordersShortcut() - listOf(readerPreferences.cropBorders(), readerPreferences.cropBordersWebtoon()) - .forEach { pref -> - pref.changes() - .onEach { updateCropBordersShortcut() } - .launchIn(lifecycleScope) - } - - // Rotation - with(binding.actionRotation) { - setTooltip(R.string.rotation_type) - - setOnClickListener { - viewModel.openOrientationModeSelectDialog() - } - } - - // Settings sheet - with(binding.actionSettings) { - setTooltip(R.string.action_settings) - - setOnClickListener { - viewModel.openSettingsDialog() - } - } - } - - private fun updateOrientationShortcut(preference: Int) { - val orientation = OrientationType.fromPreference(preference) - binding.actionRotation.setImageResource(orientation.iconRes) - } - - private fun updateCropBordersShortcut() { - val isPagerType = ReadingModeType.isPagerType(viewModel.getMangaReadingMode()) - val enabled = if (isPagerType) { - readerPreferences.cropBorders().get() - } else { - readerPreferences.cropBordersWebtoon().get() - } - - binding.actionCropBorders.setImageResource( - if (enabled) { - R.drawable.ic_crop_24dp - } else { - R.drawable.ic_crop_off_24dp - }, - ) - } - /** * Sets the visibility of the menu according to [visible] and with an optional parameter to * [animate] the views. @@ -651,13 +593,8 @@ class ReaderActivity : BaseActivity() { */ private fun setManga(manga: Manga) { val prevViewer = viewModel.state.value.viewer - - val viewerMode = ReadingModeType.fromPreference(viewModel.getMangaReadingMode(resolveDefault = false)) - binding.actionReadingMode.setImageResource(viewerMode.iconRes) - val newViewer = ReadingModeType.toViewer(viewModel.getMangaReadingMode(), this) - updateCropBordersShortcut() if (window.sharedElementEnterTransition is MaterialContainerTransform) { // Wait until transition is complete to avoid crash on API 26 window.sharedElementEnterTransition.doOnEnd { @@ -892,7 +829,6 @@ class ReaderActivity : BaseActivity() { if (newOrientation.flag != requestedOrientation) { requestedOrientation = newOrientation.flag } - updateOrientationShortcut(viewModel.getMangaOrientationType(resolveDefault = false)) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt index e6c566690..bf6ee7c5f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt @@ -54,6 +54,7 @@ import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.runBlocking import logcat.LogPriority +import tachiyomi.core.preference.toggle import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchNonCancellable import tachiyomi.core.util.lang.withIOContext @@ -661,6 +662,15 @@ class ReaderViewModel( } } + fun toggleCropBorders(): Boolean { + val isPagerType = ReadingModeType.isPagerType(getMangaReadingMode()) + return if (isPagerType) { + readerPreferences.cropBorders().toggle() + } else { + readerPreferences.cropBordersWebtoon().toggle() + } + } + /** * Generate a filename for the given [manga] and [page] */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt index 44012376a..d7be9f9fe 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt @@ -2,10 +2,8 @@ package eu.kanade.tachiyomi.util.view -import android.content.Context import android.content.res.Resources import android.graphics.Rect -import android.graphics.drawable.Drawable import android.view.Gravity import android.view.Menu import android.view.MenuItem @@ -13,9 +11,7 @@ import android.view.View import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.annotation.MenuRes -import androidx.annotation.StringRes import androidx.appcompat.widget.PopupMenu -import androidx.appcompat.widget.TooltipCompat import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme @@ -24,7 +20,6 @@ import androidx.compose.runtime.CompositionContext import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy -import com.google.android.material.shape.MaterialShapeDrawable import eu.kanade.presentation.theme.TachiyomiTheme import eu.kanade.tachiyomi.R @@ -60,24 +55,6 @@ fun ComposeView.setComposeContent( } } -/** - * Adds a tooltip shown on long press. - * - * @param stringRes String resource for tooltip. - */ -inline fun View.setTooltip(@StringRes stringRes: Int) { - setTooltip(context.getString(stringRes)) -} - -/** - * Adds a tooltip shown on long press. - * - * @param text Text for tooltip. - */ -inline fun View.setTooltip(text: String) { - TooltipCompat.setTooltipText(this, text) -} - /** * Shows a popup menu on top of this view. * @@ -105,17 +82,6 @@ inline fun View.popupMenu( return popup } -/** - * Returns a deep copy of the provided [Drawable] - */ -inline fun T.copy(context: Context): T? { - return (constantState?.newDrawable()?.mutate() as? T).apply { - if (this is MaterialShapeDrawable) { - initializeElevationOverlay(context) - } - } -} - fun View?.isVisibleOnScreen(): Boolean { if (this == null) { return false diff --git a/app/src/main/res/drawable/ic_settings_24dp.xml b/app/src/main/res/drawable/ic_settings_24dp.xml deleted file mode 100644 index 39195ad39..000000000 --- a/app/src/main/res/drawable/ic_settings_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/layout/reader_activity.xml b/app/src/main/res/layout/reader_activity.xml index b5dd2778b..1f6039afc 100644 --- a/app/src/main/res/layout/reader_activity.xml +++ b/app/src/main/res/layout/reader_activity.xml @@ -57,83 +57,11 @@ android:layout_height="wrap_content" android:minHeight="?attr/actionBarSize" /> - - - - - - - - - - - - - - - - - + android:layout_gravity="bottom" />