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:
Damontecres 2026-09-05 12:11:30 -04:00 • committed by GitHub
parent 1f3a861c47
commit 39239dd444
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 112 additions and 39 deletions

View file

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

View file

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

View file

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