Migrate bottom reader menu to Compose

This commit is contained in:
arkon 2023-08-04 18:05:02 -04:00
parent 7308090288
commit 8680accd8e
7 changed files with 144 additions and 236 deletions

View file

@ -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),
)
}
}
}

View file

@ -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,

View file

@ -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))
}
/**

View file

@ -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]
*/

View file

@ -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 <reified T : Drawable> 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

View file

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98 0,-0.34 -0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.09,-0.16 -0.26,-0.25 -0.44,-0.25 -0.06,0 -0.12,0.01 -0.17,0.03l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.06,-0.02 -0.12,-0.03 -0.18,-0.03 -0.17,0 -0.34,0.09 -0.43,0.25l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98 0,0.33 0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.09,0.16 0.26,0.25 0.44,0.25 0.06,0 0.12,-0.01 0.17,-0.03l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.06,0.02 0.12,0.03 0.18,0.03 0.17,0 0.34,-0.09 0.43,-0.25l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM17.45,11.27c0.04,0.31 0.05,0.52 0.05,0.73 0,0.21 -0.02,0.43 -0.05,0.73l-0.14,1.13 0.89,0.7 1.08,0.84 -0.7,1.21 -1.27,-0.51 -1.04,-0.42 -0.9,0.68c-0.43,0.32 -0.84,0.56 -1.25,0.73l-1.06,0.43 -0.16,1.13 -0.2,1.35h-1.4l-0.19,-1.35 -0.16,-1.13 -1.06,-0.43c-0.43,-0.18 -0.83,-0.41 -1.23,-0.71l-0.91,-0.7 -1.06,0.43 -1.27,0.51 -0.7,-1.21 1.08,-0.84 0.89,-0.7 -0.14,-1.13c-0.03,-0.31 -0.05,-0.54 -0.05,-0.74s0.02,-0.43 0.05,-0.73l0.14,-1.13 -0.89,-0.7 -1.08,-0.84 0.7,-1.21 1.27,0.51 1.04,0.42 0.9,-0.68c0.43,-0.32 0.84,-0.56 1.25,-0.73l1.06,-0.43 0.16,-1.13 0.2,-1.35h1.39l0.19,1.35 0.16,1.13 1.06,0.43c0.43,0.18 0.83,0.41 1.23,0.71l0.91,0.7 1.06,-0.43 1.27,-0.51 0.7,1.21 -1.07,0.85 -0.89,0.7 0.14,1.13zM12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,14c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z" />
</vector>

View file

@ -57,83 +57,11 @@
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize" />
<LinearLayout
<androidx.compose.ui.platform.ComposeView
android:id="@+id/reader_menu_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:orientation="vertical">
<androidx.compose.ui.platform.ComposeView
android:id="@+id/reader_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layoutDirection="ltr" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/toolbar_bottom"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_gravity="bottom"
android:clickable="true"
tools:ignore="KeyboardInaccessibleWidget">
<ImageButton
android:id="@+id/action_reading_mode"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/viewer"
android:padding="@dimen/screen_edge_margin"
app:layout_constraintEnd_toStartOf="@id/action_crop_borders"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_reader_default_24dp"
app:tint="?attr/colorOnSurface" />
<ImageButton
android:id="@+id/action_crop_borders"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/pref_crop_borders"
android:padding="@dimen/screen_edge_margin"
app:layout_constraintEnd_toStartOf="@id/action_rotation"
app:layout_constraintStart_toEndOf="@+id/action_reading_mode"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_crop_24dp"
app:tint="?attr/colorOnSurface" />
<ImageButton
android:id="@+id/action_rotation"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/pref_rotation_type"
android:padding="@dimen/screen_edge_margin"
app:layout_constraintEnd_toStartOf="@id/action_settings"
app:layout_constraintStart_toEndOf="@+id/action_crop_borders"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_screen_rotation_24dp"
app:tint="?attr/colorOnSurface" />
<ImageButton
android:id="@+id/action_settings"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/action_settings"
android:padding="@dimen/screen_edge_margin"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/action_rotation"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_settings_24dp"
app:tint="?attr/colorOnSurface" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
android:layout_gravity="bottom" />
</FrameLayout>