mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Redesign alphabet picker to fit all letters on screen (#480)
- Switch to tv.material3.Button with ButtonDefaults for better styling - Add opacity changes based on focus (0.85f focused, 0.2f unfocused) - Keep selected letter fully visible when picker is unfocused - Reduce button size from 24.dp to 14.dp to fit all letters - Reduce spacing between letters (1.1.dp vertical, 2.dp horizontal) - Use transparent backgrounds for unfocused letters - Use border color for selected letter when focused, tertiary when unfocused - Add Box with CircleShape clip to prevent focus indicator overflow - Reduce font size by 15% - Add 16.dp end padding to push picker away from screen edge When using the 'Show details' view option, the alphabet picker will still overflow the bottom edge of the screen. I find this an acceptable option though, as the backdrop pushes the card grid further down the page. Closes https://github.com/damontecres/Wholphin/issues/386 >[!NOTE] > AI was used in the making of this PR --------- Co-authored-by: Damontecres <damontecres@gmail.com>
This commit is contained in:
parent
801ff5e67b
commit
2908f539e2
1 changed files with 86 additions and 32 deletions
|
|
@ -22,6 +22,7 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||||
import androidx.compose.foundation.lazy.layout.LazyLayoutCacheWindow
|
import androidx.compose.foundation.lazy.layout.LazyLayoutCacheWindow
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
|
@ -33,11 +34,14 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
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.draw.alpha
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusProperties
|
import androidx.compose.ui.focus.focusProperties
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.focus.focusRestorer
|
import androidx.compose.ui.focus.focusRestorer
|
||||||
import androidx.compose.ui.focus.onFocusChanged
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.input.key.Key
|
import androidx.compose.ui.input.key.Key
|
||||||
import androidx.compose.ui.input.key.KeyEventType
|
import androidx.compose.ui.input.key.KeyEventType
|
||||||
import androidx.compose.ui.input.key.key
|
import androidx.compose.ui.input.key.key
|
||||||
|
|
@ -49,6 +53,8 @@ import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.tv.material3.Button
|
||||||
|
import androidx.tv.material3.ButtonDefaults
|
||||||
import androidx.tv.material3.LocalContentColor
|
import androidx.tv.material3.LocalContentColor
|
||||||
import androidx.tv.material3.MaterialTheme
|
import androidx.tv.material3.MaterialTheme
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
|
|
@ -57,8 +63,6 @@ import com.github.damontecres.wholphin.data.model.BaseItem
|
||||||
import com.github.damontecres.wholphin.ui.AppColors
|
import com.github.damontecres.wholphin.ui.AppColors
|
||||||
import com.github.damontecres.wholphin.ui.FontAwesome
|
import com.github.damontecres.wholphin.ui.FontAwesome
|
||||||
import com.github.damontecres.wholphin.ui.cards.GridCard
|
import com.github.damontecres.wholphin.ui.cards.GridCard
|
||||||
import com.github.damontecres.wholphin.ui.components.Button
|
|
||||||
import com.github.damontecres.wholphin.ui.components.TextButton
|
|
||||||
import com.github.damontecres.wholphin.ui.ifElse
|
import com.github.damontecres.wholphin.ui.ifElse
|
||||||
import com.github.damontecres.wholphin.ui.playback.isBackwardButton
|
import com.github.damontecres.wholphin.ui.playback.isBackwardButton
|
||||||
import com.github.damontecres.wholphin.ui.playback.isForwardButton
|
import com.github.damontecres.wholphin.ui.playback.isForwardButton
|
||||||
|
|
@ -366,7 +370,11 @@ fun CardGrid(
|
||||||
AlphabetButtons(
|
AlphabetButtons(
|
||||||
letters = letters,
|
letters = letters,
|
||||||
currentLetter = currentLetter,
|
currentLetter = currentLetter,
|
||||||
modifier = Modifier.align(Alignment.CenterVertically),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.align(Alignment.CenterVertically)
|
||||||
|
.padding(end = 16.dp),
|
||||||
|
// Add end padding to push away from edge
|
||||||
letterClicked = { letter ->
|
letterClicked = { letter ->
|
||||||
scope.launch(ExceptionHandler()) {
|
scope.launch(ExceptionHandler()) {
|
||||||
val jumpPosition =
|
val jumpPosition =
|
||||||
|
|
@ -375,6 +383,7 @@ fun CardGrid(
|
||||||
}
|
}
|
||||||
Timber.d("Alphabet jump to $jumpPosition")
|
Timber.d("Alphabet jump to $jumpPosition")
|
||||||
if (jumpPosition >= 0) {
|
if (jumpPosition >= 0) {
|
||||||
|
pager.getOrNull(jumpPosition)
|
||||||
gridState.scrollToItem(jumpPosition)
|
gridState.scrollToItem(jumpPosition)
|
||||||
focusOn(jumpPosition)
|
focusOn(jumpPosition)
|
||||||
alphabetFocus = true
|
alphabetFocus = true
|
||||||
|
|
@ -428,7 +437,6 @@ fun AlphabetButtons(
|
||||||
letterClicked: (Char) -> Unit,
|
letterClicked: (Char) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
|
|
@ -440,17 +448,27 @@ fun AlphabetButtons(
|
||||||
listState.layoutInfo.visibleItemsInfo
|
listState.layoutInfo.visibleItemsInfo
|
||||||
.lastOrNull()
|
.lastOrNull()
|
||||||
?.index ?: -1
|
?.index ?: -1
|
||||||
if (index < firstVisibleItemIndex || index > lastVisibleItemIndex) {
|
if (index !in firstVisibleItemIndex..lastVisibleItemIndex) {
|
||||||
listState.animateScrollToItem(index)
|
listState.animateScrollToItem(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Focus & interaction states for each letter button
|
||||||
val focusRequesters = remember { List(letters.length) { FocusRequester() } }
|
val focusRequesters = remember { List(letters.length) { FocusRequester() } }
|
||||||
|
val interactionSources = remember { List(letters.length) { MutableInteractionSource() } }
|
||||||
|
|
||||||
|
// Track if the entire alphabet picker component has focus
|
||||||
|
var alphabetPickerFocused by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
contentPadding = PaddingValues(4.dp),
|
contentPadding = PaddingValues(vertical = 1.1.dp, horizontal = 2.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(1.1.dp),
|
||||||
state = listState,
|
state = listState,
|
||||||
modifier =
|
modifier =
|
||||||
modifier.focusProperties {
|
modifier
|
||||||
|
.onFocusChanged { focusState ->
|
||||||
|
alphabetPickerFocused = focusState.hasFocus
|
||||||
|
}.focusProperties {
|
||||||
onEnter = {
|
onEnter = {
|
||||||
focusRequesters[index.coerceIn(0, letters.length - 1)].tryRequestFocus()
|
focusRequesters[index.coerceIn(0, letters.length - 1)].tryRequestFocus()
|
||||||
}
|
}
|
||||||
|
|
@ -460,32 +478,68 @@ fun AlphabetButtons(
|
||||||
letters.length,
|
letters.length,
|
||||||
key = { letters[it] },
|
key = { letters[it] },
|
||||||
) { index ->
|
) { index ->
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = interactionSources[index]
|
||||||
val focused by interactionSource.collectIsFocusedAsState()
|
val focused by interactionSource.collectIsFocusedAsState()
|
||||||
TextButton(
|
|
||||||
|
val isCurrentLetter = letters[index] == currentLetter
|
||||||
|
// Apply alpha to individual items, but keep selected letter fully visible when picker is unfocused
|
||||||
|
val itemAlpha =
|
||||||
|
when {
|
||||||
|
isCurrentLetter && !alphabetPickerFocused -> 1f
|
||||||
|
alphabetPickerFocused -> .85f
|
||||||
|
else -> .25f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only show circle background for the current letter (or when focused)
|
||||||
|
// Wrap in Box with clipping to prevent focus indicator from overflowing
|
||||||
|
Box(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.size(24.dp)
|
.size(14.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.alpha(itemAlpha),
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.size(14.dp)
|
||||||
.focusRequester(focusRequesters[index]),
|
.focusRequester(focusRequesters[index]),
|
||||||
contentPadding = PaddingValues(2.dp),
|
contentPadding = PaddingValues(0.dp), // No padding to maximize text space
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
onClick = {
|
onClick = {
|
||||||
letterClicked.invoke(letters[index])
|
letterClicked.invoke(letters[index])
|
||||||
},
|
},
|
||||||
) {
|
colors =
|
||||||
val color =
|
if (isCurrentLetter || focused) {
|
||||||
if (!focused && letters[index] == currentLetter) {
|
// Use default button colors for current letter or focused
|
||||||
MaterialTheme.colorScheme.tertiary
|
ButtonDefaults.colors()
|
||||||
} else {
|
} else {
|
||||||
LocalContentColor.current
|
// Transparent background for non-current letters (no circle)
|
||||||
|
ButtonDefaults.colors(
|
||||||
|
containerColor = Color.Transparent,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||||
|
focusedContainerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
focusedContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
// Use border color for selected letter when focused, tertiary for unfocused-selected
|
||||||
|
val color =
|
||||||
|
when {
|
||||||
|
isCurrentLetter && focused -> MaterialTheme.colorScheme.border
|
||||||
|
isCurrentLetter -> MaterialTheme.colorScheme.tertiary
|
||||||
|
focused -> LocalContentColor.current
|
||||||
|
else -> MaterialTheme.colorScheme.onSurface
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
text = letters[index].toString(),
|
text = letters[index].toString(),
|
||||||
color = color,
|
color = color,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue