Add wrap around scrolling to settings pages (#1937)
## Description Add scrolling from top to bottom and vice versa. This makes it quicker to get to Advanced Settings for example. ### Related issues N/A ### Testing Emulator ## Screenshots N/A ## AI or LLM usage None
This commit is contained in:
parent
1f3a861c47
commit
39239dd444
4 changed files with 112 additions and 39 deletions
|
|
@ -1,5 +1,19 @@
|
|||
package com.github.damontecres.wholphin.preferences
|
||||
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.input.key.KeyEventType
|
||||
import androidx.compose.ui.input.key.onKeyEvent
|
||||
import androidx.compose.ui.input.key.type
|
||||
import com.github.damontecres.wholphin.ui.ifElse
|
||||
import com.github.damontecres.wholphin.ui.playback.isDown
|
||||
import com.github.damontecres.wholphin.ui.playback.isUp
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
|
|
@ -10,3 +24,48 @@ val PlaybackPreferences.skipBackOnResume: Duration?
|
|||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds modifiers so that pressing Up on the first element scrolls to the bottom and vice versa
|
||||
*/
|
||||
@Composable
|
||||
fun Modifier.lazyListWrapScrolling(
|
||||
state: LazyListState,
|
||||
focused: Boolean,
|
||||
isFirst: Boolean,
|
||||
isLast: Boolean,
|
||||
firstFocusRequester: FocusRequester,
|
||||
lastFocusRequester: FocusRequester,
|
||||
): Modifier {
|
||||
val scope = rememberCoroutineScope()
|
||||
return this
|
||||
.ifElse(
|
||||
isFirst,
|
||||
Modifier
|
||||
.focusRequester(firstFocusRequester)
|
||||
.onKeyEvent {
|
||||
if (focused && isUp(it) && it.type == KeyEventType.KeyDown) {
|
||||
scope.launch {
|
||||
state.animateScrollToItem(state.layoutInfo.totalItemsCount - 1)
|
||||
lastFocusRequester.tryRequestFocus()
|
||||
}
|
||||
return@onKeyEvent true
|
||||
}
|
||||
false
|
||||
},
|
||||
).ifElse(
|
||||
isLast,
|
||||
Modifier
|
||||
.focusRequester(lastFocusRequester)
|
||||
.onKeyEvent {
|
||||
if (focused && isDown(it) && it.type == KeyEventType.KeyDown) {
|
||||
scope.launch {
|
||||
state.animateScrollToItem(0)
|
||||
firstFocusRequester.tryRequestFocus()
|
||||
}
|
||||
return@onKeyEvent true
|
||||
}
|
||||
false
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,15 +63,12 @@ import androidx.tv.material3.Text
|
|||
import androidx.tv.material3.surfaceColorAtElevation
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.TrackIndex
|
||||
import com.github.damontecres.wholphin.preferences.lazyListWrapScrolling
|
||||
import com.github.damontecres.wholphin.ui.FontAwesome
|
||||
import com.github.damontecres.wholphin.ui.formatBitrate
|
||||
import com.github.damontecres.wholphin.ui.ifElse
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.playback.SimpleMediaStream
|
||||
import com.github.damontecres.wholphin.ui.playback.isDown
|
||||
import com.github.damontecres.wholphin.ui.playback.isUp
|
||||
import com.github.damontecres.wholphin.ui.roundMinutes
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -322,30 +319,13 @@ fun DialogPopupContent(
|
|||
modifier =
|
||||
Modifier
|
||||
.focusRequester(focusRequesters[index])
|
||||
.ifElse(
|
||||
.lazyListWrapScrolling(
|
||||
listState,
|
||||
focused,
|
||||
index == 0,
|
||||
Modifier.onKeyEvent {
|
||||
if (focused && isUp(it) && it.type == KeyEventType.KeyDown) {
|
||||
scope.launch {
|
||||
listState.animateScrollToItem(dialogItems.lastIndex)
|
||||
focusRequesters[dialogItems.lastIndex].tryRequestFocus()
|
||||
}
|
||||
return@onKeyEvent true
|
||||
}
|
||||
false
|
||||
},
|
||||
).ifElse(
|
||||
index == dialogItems.lastIndex,
|
||||
Modifier.onKeyEvent {
|
||||
if (focused && isDown(it) && it.type == KeyEventType.KeyDown) {
|
||||
scope.launch {
|
||||
listState.animateScrollToItem(0)
|
||||
focusRequesters[0].tryRequestFocus()
|
||||
}
|
||||
return@onKeyEvent true
|
||||
}
|
||||
false
|
||||
},
|
||||
focusRequesters[0],
|
||||
focusRequesters[dialogItems.lastIndex],
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ import com.github.damontecres.wholphin.preferences.SkipSegmentPreferences
|
|||
import com.github.damontecres.wholphin.preferences.advancedPreferences
|
||||
import com.github.damontecres.wholphin.preferences.basicPreferences
|
||||
import com.github.damontecres.wholphin.preferences.experimentalPreferences
|
||||
import com.github.damontecres.wholphin.preferences.lazyListWrapScrolling
|
||||
import com.github.damontecres.wholphin.preferences.screensaverPreferences
|
||||
import com.github.damontecres.wholphin.preferences.updatePlaybackPreferences
|
||||
import com.github.damontecres.wholphin.services.Release
|
||||
|
|
@ -99,6 +100,9 @@ fun PreferencesContent(
|
|||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val firstFocusRequester = remember { FocusRequester() }
|
||||
val lastFocusRequester = remember { FocusRequester() }
|
||||
|
||||
var focusedIndex by rememberSaveable { mutableStateOf(Pair(0, 0)) }
|
||||
val state = rememberLazyListState()
|
||||
var preferences by remember { mutableStateOf(initialPreferences) }
|
||||
|
|
@ -135,7 +139,7 @@ fun PreferencesContent(
|
|||
}
|
||||
}
|
||||
|
||||
val movementSounds = true
|
||||
val movementSounds = false
|
||||
val installedVersion = updateVM.currentVersion
|
||||
val updateAvailable =
|
||||
remember(updateState.release) {
|
||||
|
|
@ -185,6 +189,12 @@ fun PreferencesContent(
|
|||
}
|
||||
}
|
||||
|
||||
val showUpdate =
|
||||
UpdateChecker.ACTIVE &&
|
||||
preferenceScreenOption == PreferenceScreenOption.BASIC &&
|
||||
preferences.autoCheckForUpdates &&
|
||||
updateAvailable
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = fadeIn() + slideInHorizontally { it / 2 },
|
||||
|
|
@ -214,11 +224,7 @@ fun PreferencesContent(
|
|||
contentPadding = PaddingValues(16.dp),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
if (UpdateChecker.ACTIVE &&
|
||||
preferenceScreenOption == PreferenceScreenOption.BASIC &&
|
||||
preferences.autoCheckForUpdates &&
|
||||
updateAvailable
|
||||
) {
|
||||
if (showUpdate) {
|
||||
item {
|
||||
val updateFocusRequester = remember { FocusRequester() }
|
||||
LaunchedEffect(Unit) {
|
||||
|
|
@ -237,6 +243,7 @@ fun PreferencesContent(
|
|||
modifier =
|
||||
Modifier
|
||||
.focusRequester(updateFocusRequester)
|
||||
.focusRequester(firstFocusRequester)
|
||||
.playSoundOnFocus(movementSounds),
|
||||
)
|
||||
}
|
||||
|
|
@ -262,15 +269,25 @@ fun PreferencesContent(
|
|||
.flatten()
|
||||
groupPreferences.forEachIndexed { prefIndex, pref ->
|
||||
pref as AppPreference<AppPreferences, Any>
|
||||
val isFirst = groupIndex == 0 && prefIndex == 0 && !showUpdate
|
||||
val isLast =
|
||||
groupIndex == prefList.lastIndex && prefIndex == groupPreferences.lastIndex
|
||||
item {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val focused = interactionSource.collectIsFocusedAsState().value
|
||||
val focusModifier =
|
||||
Modifier
|
||||
.ifElse(
|
||||
groupIndex == focusedIndex.first && prefIndex == focusedIndex.second,
|
||||
Modifier.focusRequester(focusRequester),
|
||||
).lazyListWrapScrolling(
|
||||
state,
|
||||
focused,
|
||||
isFirst,
|
||||
isLast,
|
||||
firstFocusRequester,
|
||||
lastFocusRequester,
|
||||
)
|
||||
val focused = interactionSource.collectIsFocusedAsState().value
|
||||
LaunchedEffect(focused) {
|
||||
if (focused) {
|
||||
focusedIndex = Pair(groupIndex, prefIndex)
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import androidx.tv.material3.Text
|
|||
import androidx.tv.material3.surfaceColorAtElevation
|
||||
import com.github.damontecres.wholphin.preferences.AppPreference
|
||||
import com.github.damontecres.wholphin.preferences.SubtitlePreferences
|
||||
import com.github.damontecres.wholphin.preferences.lazyListWrapScrolling
|
||||
import com.github.damontecres.wholphin.preferences.resetSubtitles
|
||||
import com.github.damontecres.wholphin.ui.ifElse
|
||||
import com.github.damontecres.wholphin.ui.preferences.ClickPreference
|
||||
|
|
@ -64,6 +65,8 @@ fun SubtitlePreferencesContent(
|
|||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val firstFocusRequester = remember { FocusRequester() }
|
||||
val lastFocusRequester = remember { FocusRequester() }
|
||||
var focusedIndex by rememberSaveable { mutableStateOf(Pair(0, 0)) }
|
||||
val state = rememberLazyListState()
|
||||
|
||||
|
|
@ -123,6 +126,9 @@ fun SubtitlePreferencesContent(
|
|||
groupPreferences.forEachIndexed { prefIndex, pref ->
|
||||
pref as AppPreference<SubtitlePreferences, Any>
|
||||
item {
|
||||
val isFirst = groupIndex == 0 && prefIndex == 0
|
||||
val isLast =
|
||||
groupIndex == prefList.lastIndex && prefIndex == groupPreferences.lastIndex
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
||||
val focused = interactionSource.collectIsFocusedAsState().value
|
||||
|
|
@ -137,6 +143,21 @@ fun SubtitlePreferencesContent(
|
|||
bringIntoViewRequester.bringIntoView()
|
||||
}
|
||||
}
|
||||
val focusModifier =
|
||||
Modifier
|
||||
.ifElse(
|
||||
groupIndex == focusedIndex.first && prefIndex == focusedIndex.second,
|
||||
Modifier.focusRequester(focusRequester),
|
||||
).bringIntoViewRequester(bringIntoViewRequester)
|
||||
.lazyListWrapScrolling(
|
||||
state,
|
||||
focused,
|
||||
isFirst,
|
||||
isLast,
|
||||
firstFocusRequester,
|
||||
lastFocusRequester,
|
||||
)
|
||||
|
||||
when (pref) {
|
||||
SubtitleSettings.Reset -> {
|
||||
ClickPreference(
|
||||
|
|
@ -152,6 +173,7 @@ fun SubtitlePreferencesContent(
|
|||
}
|
||||
},
|
||||
interactionSource = interactionSource,
|
||||
modifier = focusModifier,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -187,12 +209,7 @@ fun SubtitlePreferencesContent(
|
|||
}
|
||||
},
|
||||
interactionSource = interactionSource,
|
||||
modifier =
|
||||
Modifier
|
||||
.ifElse(
|
||||
groupIndex == focusedIndex.first && prefIndex == focusedIndex.second,
|
||||
Modifier.focusRequester(focusRequester),
|
||||
).bringIntoViewRequester(bringIntoViewRequester),
|
||||
modifier = focusModifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue