feat(android): artist fast-scroll index bar with letter bubble
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
dd484df636
commit
9ffd071c58
1 file changed
+97
-2
@@ -9,6 +9,7 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.border
|
||||
@@ -20,9 +21,12 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.input.pointer.PointerEventType
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
@@ -37,6 +41,7 @@ import com.novasonic.app.ui.theme.*
|
||||
import com.novasonic.app.viewmodel.AlbumSort
|
||||
import com.novasonic.app.viewmodel.LibraryTab
|
||||
import com.novasonic.app.viewmodel.LibraryViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun LibraryScreen(
|
||||
@@ -145,8 +150,26 @@ fun LibraryScreen(
|
||||
}
|
||||
}
|
||||
|
||||
LibraryTab.ARTISTS -> LazyColumn(
|
||||
contentPadding = PaddingValues(vertical = 8.dp),
|
||||
LibraryTab.ARTISTS -> {
|
||||
val listState = rememberLazyListState()
|
||||
val scope = rememberCoroutineScope()
|
||||
val letters = remember(state.artistIndexes) { state.artistIndexes.map { it.name } }
|
||||
// Map letter → flat LazyColumn index of its header item
|
||||
val letterToIndex = remember(state.artistIndexes) {
|
||||
buildMap<String, Int> {
|
||||
var flat = 0
|
||||
state.artistIndexes.forEach { idx ->
|
||||
put(idx.name, flat)
|
||||
flat += 1 + idx.artist.size
|
||||
}
|
||||
}
|
||||
}
|
||||
var activeLetter by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
contentPadding = PaddingValues(top = 8.dp, bottom = 8.dp, end = 28.dp),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
state.artistIndexes.forEach { idx ->
|
||||
@@ -186,6 +209,78 @@ fun LibraryScreen(
|
||||
}
|
||||
}
|
||||
|
||||
if (letters.isNotEmpty()) {
|
||||
// Bubble showing active letter while dragging
|
||||
activeLetter?.let { letter ->
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterEnd)
|
||||
.offset(x = (-44).dp)
|
||||
.size(52.dp)
|
||||
.clip(CircleShape)
|
||||
.background(NsAccent),
|
||||
) {
|
||||
Text(
|
||||
text = letter,
|
||||
fontSize = 22.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = NsBackground,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Vertical letter strip
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterEnd)
|
||||
.fillMaxHeight()
|
||||
.width(28.dp)
|
||||
.pointerInput(letters) {
|
||||
awaitPointerEventScope {
|
||||
while (true) {
|
||||
val event = awaitPointerEvent()
|
||||
val y = event.changes.firstOrNull()?.position?.y ?: continue
|
||||
val idx = ((y / size.height) * letters.size)
|
||||
.toInt().coerceIn(0, letters.lastIndex)
|
||||
val letter = letters[idx]
|
||||
when (event.type) {
|
||||
PointerEventType.Press,
|
||||
PointerEventType.Move -> {
|
||||
event.changes.forEach { it.consume() }
|
||||
if (letter != activeLetter) {
|
||||
activeLetter = letter
|
||||
letterToIndex[letter]?.let { pos ->
|
||||
scope.launch { listState.scrollToItem(pos) }
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> activeLetter = null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.SpaceEvenly,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
letters.forEach { letter ->
|
||||
Text(
|
||||
text = letter,
|
||||
fontSize = 10.sp,
|
||||
fontWeight = if (letter == activeLetter) FontWeight.Bold else FontWeight.Normal,
|
||||
color = if (letter == activeLetter) NsAccent else NsOnSurface3,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LibraryTab.PLAYLISTS -> LazyVerticalGrid(
|
||||
columns = GridCells.Adaptive(minSize = 150.dp),
|
||||
contentPadding = PaddingValues(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 80.dp),
|
||||
|
||||
Reference in new issue
Block a user