mirror of
https://github.com/Suwayomi/TachideskJUI.git
synced 2025-12-10 14:52:03 +01:00
Improve hotkey handling, add support for volume buttons and spacebar. Closes #60
This commit is contained in:
@@ -12,16 +12,8 @@ import android.os.Bundle
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.input.key.KeyEvent
|
||||
import androidx.compose.ui.input.key.key
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import ca.gosyer.jui.ui.base.model.StableHolder
|
||||
import ca.gosyer.jui.ui.base.theme.AppTheme
|
||||
import ca.gosyer.jui.ui.reader.ReaderMenu
|
||||
import ca.gosyer.jui.ui.reader.supportedKeyList
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ReaderActivity : AppCompatActivity() {
|
||||
|
||||
@@ -35,9 +27,6 @@ class ReaderActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private val hotkeyFlow = MutableSharedFlow<KeyEvent>()
|
||||
private val hotkeyFlowHolder = StableHolder(hotkeyFlow.asSharedFlow())
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val hooks = AppComponent.getInstance(applicationContext).hooks
|
||||
@@ -55,26 +44,10 @@ class ReaderActivity : AppCompatActivity() {
|
||||
ReaderMenu(
|
||||
chapterIndex = chapterIndex,
|
||||
mangaId = mangaId,
|
||||
hotkeyFlowHolder = hotkeyFlowHolder,
|
||||
onCloseRequest = onBackPressedDispatcher::onBackPressed
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onKeyUp(keyCode: Int, event: android.view.KeyEvent?): Boolean {
|
||||
@Suppress("KotlinConstantConditions")
|
||||
event ?: return super.onKeyUp(keyCode, event)
|
||||
val composeKeyEvent = KeyEvent(event)
|
||||
lifecycleScope.launch {
|
||||
hotkeyFlow.emit(composeKeyEvent)
|
||||
}
|
||||
|
||||
return if (composeKeyEvent.key in supportedKeyList) {
|
||||
true
|
||||
} else {
|
||||
super.onKeyUp(keyCode, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
@@ -44,15 +45,22 @@ import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.graphics.FilterQuality
|
||||
import androidx.compose.ui.input.key.Key
|
||||
import androidx.compose.ui.input.key.KeyEvent
|
||||
import androidx.compose.ui.input.key.KeyEventType
|
||||
import androidx.compose.ui.input.key.key
|
||||
import androidx.compose.ui.input.key.onKeyEvent
|
||||
import androidx.compose.ui.input.key.type
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -86,20 +94,8 @@ import ca.gosyer.jui.uicore.resources.stringResource
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
val supportedKeyList = listOf(
|
||||
Key.W,
|
||||
Key.DirectionUp,
|
||||
Key.S,
|
||||
Key.DirectionDown,
|
||||
Key.A,
|
||||
Key.DirectionLeft,
|
||||
Key.D,
|
||||
Key.DirectionRight
|
||||
)
|
||||
|
||||
expect class ReaderLauncher {
|
||||
fun launch(
|
||||
chapterIndex: Int,
|
||||
@@ -117,7 +113,6 @@ expect fun rememberReaderLauncher(): ReaderLauncher
|
||||
fun ReaderMenu(
|
||||
chapterIndex: Int,
|
||||
mangaId: Long,
|
||||
hotkeyFlowHolder: StableHolder<SharedFlow<KeyEvent>>,
|
||||
onCloseRequest: () -> Unit
|
||||
) {
|
||||
val viewModels = LocalViewModels.current
|
||||
@@ -147,18 +142,29 @@ fun ReaderMenu(
|
||||
val currentPageOffset by vm.currentPageOffset.collectAsState()
|
||||
val readerSettingsMenuOpen by vm.readerSettingsMenuOpen.collectAsState()
|
||||
|
||||
LaunchedEffect(hotkeyFlowHolder) {
|
||||
hotkeyFlowHolder.item.collectLatest {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
var hasFocus by remember { mutableStateOf(false) }
|
||||
Surface(
|
||||
Modifier
|
||||
.focusRequester(focusRequester)
|
||||
.onFocusChanged {
|
||||
hasFocus = it.hasFocus
|
||||
}
|
||||
.focusable()
|
||||
.onKeyEvent {
|
||||
if (it.type != KeyEventType.KeyDown) return@onKeyEvent false
|
||||
when (it.key) {
|
||||
Key.W, Key.DirectionUp -> vm.navigate(Navigation.PREV)
|
||||
Key.S, Key.DirectionDown -> vm.navigate(Navigation.NEXT)
|
||||
Key.A, Key.DirectionLeft -> vm.navigate(Navigation.LEFT)
|
||||
Key.D, Key.DirectionRight -> vm.navigate(Navigation.RIGHT)
|
||||
Key.VolumeDown -> vm.navigate(Navigation.DOWN)
|
||||
Key.VolumeUp -> vm.navigate(Navigation.UP)
|
||||
Key.Spacebar -> vm.navigate(Navigation.NEXT)
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Surface {
|
||||
) {
|
||||
Crossfade(state to chapter) { (state, chapter) ->
|
||||
if (state is ReaderChapter.State.Loaded && chapter != null) {
|
||||
if (pages.isNotEmpty()) {
|
||||
@@ -233,6 +239,13 @@ fun ReaderMenu(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
LaunchedEffect(hasFocus) {
|
||||
if (!hasFocus) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -148,7 +148,7 @@ class ReaderMenuViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun navigate(navigationRegion: Navigation) {
|
||||
fun navigate(navigationRegion: Navigation): Boolean {
|
||||
scope.launch {
|
||||
val moveTo = when (navigationRegion) {
|
||||
Navigation.MENU -> {
|
||||
@@ -165,11 +165,20 @@ class ReaderMenuViewModel @Inject constructor(
|
||||
Direction.Left -> MoveTo.Next
|
||||
else -> MoveTo.Previous
|
||||
}
|
||||
Navigation.DOWN -> when (readerModeSettings.direction.value) {
|
||||
Direction.Up -> MoveTo.Previous
|
||||
else -> MoveTo.Next
|
||||
}
|
||||
Navigation.UP -> when (readerModeSettings.direction.value) {
|
||||
Direction.Up -> MoveTo.Next
|
||||
else -> MoveTo.Previous
|
||||
}
|
||||
}
|
||||
if (moveTo != null) {
|
||||
_pageEmitter.emit(PageMove.Direction(moveTo))
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun navigate(page: Int) {
|
||||
|
||||
@@ -12,4 +12,6 @@ sealed class Navigation(val name: String) {
|
||||
object NEXT : Navigation("Next")
|
||||
object LEFT : Navigation("Left")
|
||||
object RIGHT : Navigation("Right")
|
||||
object UP : Navigation("Up")
|
||||
object DOWN : Navigation("Down")
|
||||
}
|
||||
|
||||
@@ -13,24 +13,15 @@ import androidx.compose.runtime.currentCompositionLocalContext
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.input.key.KeyEvent
|
||||
import androidx.compose.ui.input.key.KeyEventType
|
||||
import androidx.compose.ui.input.key.key
|
||||
import androidx.compose.ui.input.key.type
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.window.Window
|
||||
import androidx.compose.ui.window.WindowPosition
|
||||
import androidx.compose.ui.window.rememberWindowState
|
||||
import ca.gosyer.jui.presentation.build.BuildKonfig
|
||||
import ca.gosyer.jui.ui.base.model.StableHolder
|
||||
import ca.gosyer.jui.ui.util.lang.launchApplication
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
actual class ReaderLauncher {
|
||||
|
||||
@@ -50,9 +41,6 @@ actual class ReaderLauncher {
|
||||
DisposableEffect(isOpen) {
|
||||
isOpen?.let { (chapterIndex, mangaId) ->
|
||||
launchApplication {
|
||||
val scope = rememberCoroutineScope()
|
||||
val hotkeyFlow = remember { MutableSharedFlow<KeyEvent>() }
|
||||
val hotkeyFlowHolder = remember { StableHolder(hotkeyFlow.asSharedFlow()) }
|
||||
val windowState = rememberWindowState(
|
||||
position = WindowPosition.Aligned(Alignment.Center)
|
||||
)
|
||||
@@ -63,18 +51,10 @@ actual class ReaderLauncher {
|
||||
title = "${BuildKonfig.NAME} - Reader",
|
||||
icon = icon,
|
||||
state = windowState,
|
||||
onKeyEvent = {
|
||||
if (it.type != KeyEventType.KeyDown) return@Window false
|
||||
scope.launch {
|
||||
hotkeyFlow.emit(it)
|
||||
}
|
||||
it.key in supportedKeyList
|
||||
}
|
||||
) {
|
||||
ReaderMenu(
|
||||
chapterIndex = chapterIndex,
|
||||
mangaId = mangaId,
|
||||
hotkeyFlowHolder = hotkeyFlowHolder,
|
||||
onCloseRequest = ::exitApplication
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,12 +8,10 @@ package ca.gosyer.jui.ui.reader
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import ca.gosyer.jui.ui.base.model.StableHolder
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.Navigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
|
||||
class ReaderScreen(val chapterIndex: Int, val mangaId: Long) : Screen {
|
||||
|
||||
@@ -23,7 +21,6 @@ class ReaderScreen(val chapterIndex: Int, val mangaId: Long) : Screen {
|
||||
ReaderMenu(
|
||||
chapterIndex,
|
||||
mangaId,
|
||||
remember { StableHolder(MutableSharedFlow()) },
|
||||
navigator::pop
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user