mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +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.layout.LazyLayoutCacheWindow
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
|
|
@ -33,11 +34,14 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
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.focusProperties
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.focusRestorer
|
||||
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.KeyEventType
|
||||
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.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.MaterialTheme
|
||||
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.FontAwesome
|
||||
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.playback.isBackwardButton
|
||||
import com.github.damontecres.wholphin.ui.playback.isForwardButton
|
||||
|
|
@ -366,7 +370,11 @@ fun CardGrid(
|
|||
AlphabetButtons(
|
||||
letters = letters,
|
||||
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 ->
|
||||
scope.launch(ExceptionHandler()) {
|
||||
val jumpPosition =
|
||||
|
|
@ -375,6 +383,7 @@ fun CardGrid(
|
|||
}
|
||||
Timber.d("Alphabet jump to $jumpPosition")
|
||||
if (jumpPosition >= 0) {
|
||||
pager.getOrNull(jumpPosition)
|
||||
gridState.scrollToItem(jumpPosition)
|
||||
focusOn(jumpPosition)
|
||||
alphabetFocus = true
|
||||
|
|
@ -428,7 +437,6 @@ fun AlphabetButtons(
|
|||
letterClicked: (Char) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
|
|
@ -440,51 +448,97 @@ fun AlphabetButtons(
|
|||
listState.layoutInfo.visibleItemsInfo
|
||||
.lastOrNull()
|
||||
?.index ?: -1
|
||||
if (index < firstVisibleItemIndex || index > lastVisibleItemIndex) {
|
||||
if (index !in firstVisibleItemIndex..lastVisibleItemIndex) {
|
||||
listState.animateScrollToItem(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Focus & interaction states for each letter button
|
||||
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(
|
||||
contentPadding = PaddingValues(4.dp),
|
||||
contentPadding = PaddingValues(vertical = 1.1.dp, horizontal = 2.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(1.1.dp),
|
||||
state = listState,
|
||||
modifier =
|
||||
modifier.focusProperties {
|
||||
onEnter = {
|
||||
focusRequesters[index.coerceIn(0, letters.length - 1)].tryRequestFocus()
|
||||
}
|
||||
},
|
||||
modifier
|
||||
.onFocusChanged { focusState ->
|
||||
alphabetPickerFocused = focusState.hasFocus
|
||||
}.focusProperties {
|
||||
onEnter = {
|
||||
focusRequesters[index.coerceIn(0, letters.length - 1)].tryRequestFocus()
|
||||
}
|
||||
},
|
||||
) {
|
||||
items(
|
||||
letters.length,
|
||||
key = { letters[it] },
|
||||
) { index ->
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val interactionSource = interactionSources[index]
|
||||
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
|
||||
.size(24.dp)
|
||||
.focusRequester(focusRequesters[index]),
|
||||
contentPadding = PaddingValues(2.dp),
|
||||
interactionSource = interactionSource,
|
||||
onClick = {
|
||||
letterClicked.invoke(letters[index])
|
||||
},
|
||||
.size(14.dp)
|
||||
.clip(CircleShape)
|
||||
.alpha(itemAlpha),
|
||||
) {
|
||||
val color =
|
||||
if (!focused && letters[index] == currentLetter) {
|
||||
MaterialTheme.colorScheme.tertiary
|
||||
} else {
|
||||
LocalContentColor.current
|
||||
}
|
||||
Text(
|
||||
text = letters[index].toString(),
|
||||
color = color,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
Button(
|
||||
modifier =
|
||||
Modifier
|
||||
.size(14.dp)
|
||||
.focusRequester(focusRequesters[index]),
|
||||
contentPadding = PaddingValues(0.dp), // No padding to maximize text space
|
||||
interactionSource = interactionSource,
|
||||
onClick = {
|
||||
letterClicked.invoke(letters[index])
|
||||
},
|
||||
colors =
|
||||
if (isCurrentLetter || focused) {
|
||||
// Use default button colors for current letter or focused
|
||||
ButtonDefaults.colors()
|
||||
} else {
|
||||
// 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 = letters[index].toString(),
|
||||
color = color,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue