Improve hotkey handling, add support for volume buttons and spacebar. Closes #60

This commit is contained in:
Syer10
2022-12-31 14:28:24 -05:00
parent 7ed64bd4b6
commit 1f2b8123ea
6 changed files with 49 additions and 75 deletions

View File

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

View File

@@ -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 {
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)
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

View File

@@ -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) {

View File

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

View File

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

View File

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