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