Migrate reader shortcut menus to Compose

Contents' UIs should probably be improved, but that can happen separately.
This commit is contained in:
arkon 2023-08-04 17:34:08 -04:00
parent 400ca48456
commit 7308090288
10 changed files with 161 additions and 117 deletions

View file

@ -0,0 +1,56 @@
package eu.kanade.presentation.reader
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.FilterChip
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import eu.kanade.domain.manga.model.orientationType
import eu.kanade.presentation.components.AdaptiveSheet
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
import tachiyomi.presentation.core.components.SettingsChipRow
import tachiyomi.presentation.core.components.material.padding
private val orientationTypeOptions = OrientationType.entries.map { it.stringRes to it }
@Composable
fun OrientationModeSelectDialog(
onDismissRequest: () -> Unit,
screenModel: ReaderSettingsScreenModel,
onChange: (Int) -> Unit,
) {
val manga by screenModel.mangaFlow.collectAsState()
val orientationType = remember(manga) { OrientationType.fromPreference(manga?.orientationType?.toInt()) }
AdaptiveSheet(
onDismissRequest = onDismissRequest,
) {
Row(
modifier = Modifier.padding(vertical = 16.dp),
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
) {
SettingsChipRow(R.string.rotation_type) {
orientationTypeOptions.map { (stringRes, it) ->
FilterChip(
selected = it == orientationType,
onClick = {
screenModel.onChangeOrientation(it)
onChange(stringRes)
},
label = { Text(stringResource(stringRes)) },
)
}
}
}
}
}

View file

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.ui.reader package eu.kanade.presentation.reader
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row

View file

@ -0,0 +1,56 @@
package eu.kanade.presentation.reader
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.FilterChip
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import eu.kanade.domain.manga.model.readingModeType
import eu.kanade.presentation.components.AdaptiveSheet
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
import tachiyomi.presentation.core.components.SettingsChipRow
import tachiyomi.presentation.core.components.material.padding
private val readingModeOptions = ReadingModeType.entries.map { it.stringRes to it }
@Composable
fun ReadingModeSelectDialog(
onDismissRequest: () -> Unit,
screenModel: ReaderSettingsScreenModel,
onChange: (Int) -> Unit,
) {
val manga by screenModel.mangaFlow.collectAsState()
val readingMode = remember(manga) { ReadingModeType.fromPreference(manga?.readingModeType?.toInt()) }
AdaptiveSheet(
onDismissRequest = onDismissRequest,
) {
Row(
modifier = Modifier.padding(vertical = 16.dp),
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
) {
SettingsChipRow(R.string.pref_category_reading_mode) {
readingModeOptions.map { (stringRes, it) ->
FilterChip(
selected = it == readingMode,
onClick = {
screenModel.onChangeReadingMode(it)
onChange(stringRes)
},
label = { Text(stringResource(stringRes)) },
)
}
}
}
}
}

View file

@ -42,11 +42,12 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
pref = screenModel.preferences.fullscreen(), pref = screenModel.preferences.fullscreen(),
) )
// TODO: hide if there's no cutout if (screenModel.hasDisplayCutout) {
CheckboxItem( CheckboxItem(
label = stringResource(R.string.pref_cutout_short), label = stringResource(R.string.pref_cutout_short),
pref = screenModel.preferences.cutoutShort(), pref = screenModel.preferences.cutoutShort(),
) )
}
CheckboxItem( CheckboxItem(
label = stringResource(R.string.pref_keep_screen_on), label = stringResource(R.string.pref_keep_screen_on),

View file

@ -49,9 +49,11 @@ import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.transition.platform.MaterialContainerTransform import com.google.android.material.transition.platform.MaterialContainerTransform
import dev.chrisbanes.insetter.applyInsetter import dev.chrisbanes.insetter.applyInsetter
import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.manga.model.orientationType
import eu.kanade.presentation.reader.ChapterNavigator import eu.kanade.presentation.reader.ChapterNavigator
import eu.kanade.presentation.reader.OrientationModeSelectDialog
import eu.kanade.presentation.reader.PageIndicatorText import eu.kanade.presentation.reader.PageIndicatorText
import eu.kanade.presentation.reader.ReaderPageActionsDialog
import eu.kanade.presentation.reader.ReadingModeSelectDialog
import eu.kanade.presentation.reader.settings.ReaderSettingsDialog import eu.kanade.presentation.reader.settings.ReaderSettingsDialog
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.NotificationReceiver
@ -79,7 +81,6 @@ import eu.kanade.tachiyomi.util.system.isNightMode
import eu.kanade.tachiyomi.util.system.toShareIntent import eu.kanade.tachiyomi.util.system.toShareIntent
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.copy import eu.kanade.tachiyomi.util.view.copy
import eu.kanade.tachiyomi.util.view.popupMenu
import eu.kanade.tachiyomi.util.view.setComposeContent import eu.kanade.tachiyomi.util.view.setComposeContent
import eu.kanade.tachiyomi.util.view.setTooltip import eu.kanade.tachiyomi.util.view.setTooltip
import eu.kanade.tachiyomi.widget.listener.SimpleAnimationListener import eu.kanade.tachiyomi.widget.listener.SimpleAnimationListener
@ -124,7 +125,7 @@ class ReaderActivity : BaseActivity() {
val viewModel by viewModels<ReaderViewModel>() val viewModel by viewModels<ReaderViewModel>()
private var assistUrl: String? = null private var assistUrl: String? = null
val hasCutout by lazy { hasDisplayCutout() } private val hasCutout by lazy { hasDisplayCutout() }
/** /**
* Configuration at reader level, like background color or forced orientation. * Configuration at reader level, like background color or forced orientation.
@ -393,6 +394,7 @@ class ReaderActivity : BaseActivity() {
val settingsScreenModel = remember { val settingsScreenModel = remember {
ReaderSettingsScreenModel( ReaderSettingsScreenModel(
readerState = viewModel.state, readerState = viewModel.state,
hasDisplayCutout = hasCutout,
onChangeReadingMode = viewModel::setMangaReadingMode, onChangeReadingMode = viewModel::setMangaReadingMode,
onChangeOrientation = viewModel::setMangaOrientationType, onChangeOrientation = viewModel::setMangaOrientationType,
) )
@ -423,6 +425,30 @@ class ReaderActivity : BaseActivity() {
screenModel = settingsScreenModel, screenModel = settingsScreenModel,
) )
} }
is ReaderViewModel.Dialog.ReadingModeSelect -> {
ReadingModeSelectDialog(
onDismissRequest = onDismissRequest,
screenModel = settingsScreenModel,
onChange = { stringRes ->
menuToggleToast?.cancel()
if (!readerPreferences.showReadingMode().get()) {
menuToggleToast = toast(stringRes)
}
updateCropBordersShortcut()
},
)
}
is ReaderViewModel.Dialog.OrientationModeSelect -> {
OrientationModeSelectDialog(
onDismissRequest = onDismissRequest,
screenModel = settingsScreenModel,
onChange = { stringRes ->
menuToggleToast?.cancel()
menuToggleToast = toast(stringRes)
},
)
}
is ReaderViewModel.Dialog.PageActions -> { is ReaderViewModel.Dialog.PageActions -> {
ReaderPageActionsDialog( ReaderPageActionsDialog(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
@ -484,21 +510,7 @@ class ReaderActivity : BaseActivity() {
setTooltip(R.string.viewer) setTooltip(R.string.viewer)
setOnClickListener { setOnClickListener {
popupMenu( viewModel.openReadingModeSelectDialog()
items = ReadingModeType.entries.map { it.flagValue to it.stringRes },
selectedItemId = viewModel.getMangaReadingMode(resolveDefault = false),
) {
val newReadingMode = ReadingModeType.fromPreference(itemId)
viewModel.setMangaReadingMode(newReadingMode)
menuToggleToast?.cancel()
if (!readerPreferences.showReadingMode().get()) {
menuToggleToast = toast(newReadingMode.stringRes)
}
updateCropBordersShortcut()
}
} }
} }
@ -537,18 +549,7 @@ class ReaderActivity : BaseActivity() {
setTooltip(R.string.rotation_type) setTooltip(R.string.rotation_type)
setOnClickListener { setOnClickListener {
popupMenu( viewModel.openOrientationModeSelectDialog()
items = OrientationType.entries.map { it.flagValue to it.stringRes },
selectedItemId = viewModel.manga?.orientationType?.toInt()
?: readerPreferences.defaultOrientationType().get(),
) {
val newOrientation = OrientationType.fromPreference(itemId)
viewModel.setMangaOrientationType(newOrientation)
menuToggleToast?.cancel()
menuToggleToast = toast(newOrientation.stringRes)
}
} }
} }

View file

@ -683,6 +683,14 @@ class ReaderViewModel(
mutableState.update { it.copy(dialog = Dialog.Loading) } mutableState.update { it.copy(dialog = Dialog.Loading) }
} }
fun openReadingModeSelectDialog() {
mutableState.update { it.copy(dialog = Dialog.ReadingModeSelect) }
}
fun openOrientationModeSelectDialog() {
mutableState.update { it.copy(dialog = Dialog.OrientationModeSelect) }
}
fun openPageDialog(page: ReaderPage) { fun openPageDialog(page: ReaderPage) {
mutableState.update { it.copy(dialog = Dialog.PageActions(page)) } mutableState.update { it.copy(dialog = Dialog.PageActions(page)) }
} }
@ -863,6 +871,8 @@ class ReaderViewModel(
sealed interface Dialog { sealed interface Dialog {
data object Loading : Dialog data object Loading : Dialog
data object Settings : Dialog data object Settings : Dialog
data object ReadingModeSelect : Dialog
data object OrientationModeSelect : Dialog
data class PageActions(val page: ReaderPage) : Dialog data class PageActions(val page: ReaderPage) : Dialog
} }

View file

@ -13,6 +13,7 @@ import uy.kohesive.injekt.api.get
class ReaderSettingsScreenModel( class ReaderSettingsScreenModel(
readerState: StateFlow<ReaderViewModel.State>, readerState: StateFlow<ReaderViewModel.State>,
val hasDisplayCutout: Boolean,
val onChangeReadingMode: (ReadingModeType) -> Unit, val onChangeReadingMode: (ReadingModeType) -> Unit,
val onChangeOrientation: (OrientationType) -> Unit, val onChangeOrientation: (OrientationType) -> Unit,
val preferences: ReaderPreferences = Injekt.get(), val preferences: ReaderPreferences = Injekt.get(),

View file

@ -7,20 +7,13 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Color
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.PowerManager import android.os.PowerManager
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.appcompat.view.ContextThemeWrapper import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.content.PermissionChecker import androidx.core.content.PermissionChecker
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.core.graphics.alpha
import androidx.core.graphics.blue
import androidx.core.graphics.green
import androidx.core.graphics.red
import androidx.core.net.toUri import androidx.core.net.toUri
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import eu.kanade.domain.ui.UiPreferences import eu.kanade.domain.ui.UiPreferences
@ -35,7 +28,6 @@ import tachiyomi.core.util.system.logcat
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.File import java.io.File
import kotlin.math.roundToInt
/** /**
* Copies a string to clipboard * Copies a string to clipboard
@ -69,25 +61,6 @@ fun Context.copyToClipboard(label: String, content: String) {
*/ */
fun Context.hasPermission(permission: String) = PermissionChecker.checkSelfPermission(this, permission) == PermissionChecker.PERMISSION_GRANTED fun Context.hasPermission(permission: String) = PermissionChecker.checkSelfPermission(this, permission) == PermissionChecker.PERMISSION_GRANTED
/**
* Returns the color for the given attribute.
*
* @param resource the attribute.
* @param alphaFactor the alpha number [0,1].
*/
@ColorInt fun Context.getResourceColor(@AttrRes resource: Int, alphaFactor: Float = 1f): Int {
val typedArray = obtainStyledAttributes(intArrayOf(resource))
val color = typedArray.getColor(0, 0)
typedArray.recycle()
if (alphaFactor < 1f) {
val alpha = (color.alpha * alphaFactor).roundToInt()
return Color.argb(alpha, color.red, color.green, color.blue)
}
return color
}
val Context.powerManager: PowerManager val Context.powerManager: PowerManager
get() = getSystemService()!! get() = getSystemService()!!

View file

@ -2,7 +2,6 @@
package eu.kanade.tachiyomi.util.view package eu.kanade.tachiyomi.util.view
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import android.graphics.Rect import android.graphics.Rect
@ -15,8 +14,6 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.annotation.MenuRes import androidx.annotation.MenuRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.content.res.AppCompatResources
import androidx.appcompat.view.menu.MenuBuilder
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.TooltipCompat import androidx.appcompat.widget.TooltipCompat
import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalContentColor
@ -27,11 +24,9 @@ import androidx.compose.runtime.CompositionContext
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.core.view.forEach
import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.shape.MaterialShapeDrawable
import eu.kanade.presentation.theme.TachiyomiTheme import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.getResourceColor
inline fun ComponentActivity.setComposeContent( inline fun ComponentActivity.setComposeContent(
parent: CompositionContext? = null, parent: CompositionContext? = null,
@ -110,46 +105,6 @@ inline fun View.popupMenu(
return popup return popup
} }
/**
* Shows a popup menu on top of this view.
*
* @param items menu item names to inflate the menu with. List of itemId to stringRes pairs.
* @param selectedItemId optionally show a checkmark beside an item with this itemId.
* @param onMenuItemClick function to execute when a menu item is clicked.
*/
@SuppressLint("RestrictedApi")
inline fun View.popupMenu(
items: List<Pair<Int, Int>>,
selectedItemId: Int? = null,
noinline onMenuItemClick: MenuItem.() -> Unit,
): PopupMenu {
val popup = PopupMenu(context, this, Gravity.NO_GRAVITY, R.attr.actionOverflowMenuStyle, 0)
items.forEach { (id, stringRes) ->
popup.menu.add(0, id, 0, stringRes)
}
if (selectedItemId != null) {
(popup.menu as? MenuBuilder)?.setOptionalIconsVisible(true)
val emptyIcon = AppCompatResources.getDrawable(context, R.drawable.ic_blank_24dp)
popup.menu.forEach { item ->
item.icon = when (item.itemId) {
selectedItemId -> AppCompatResources.getDrawable(context, R.drawable.ic_check_24dp)?.mutate()?.apply {
setTint(context.getResourceColor(android.R.attr.textColorPrimary))
}
else -> emptyIcon
}
}
}
popup.setOnMenuItemClickListener {
it.onMenuItemClick()
true
}
popup.show()
return popup
}
/** /**
* Returns a deep copy of the provided [Drawable] * Returns a deep copy of the provided [Drawable]
*/ */

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.0"
android:viewportHeight="24.0">
<path
android:fillColor="#000"
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" />
</vector>