feat(android): swipe to change Library tabs
android / Build + lint + test (push) Has been cancelled

Operator: tabs in the Library view should feel swipeable, not just
tappable. Replace the selectedTab Int state + when-block content
with HorizontalPager whose state drives the PrimaryScrollableTabRow.
Tap routes through animateScrollToPage so swipe + tap share one
source of truth.

Horizontal pager gestures don't conflict with the LazyVerticalGrid
inside each tab (different axes) or with PullToRefreshScaffold's
vertical pull (different axes). HorizontalPager renders only the
current page by default; adjacent tabs remain composed during the
swipe but not eager-mounted at start.
This commit is contained in:
2026-06-02 10:04:38 -04:00
parent b83a6a4bdb
commit 9b3ec65476
@@ -4,7 +4,6 @@ package com.fabledsword.minstrel.library.ui
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
@@ -12,6 +11,8 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@@ -22,11 +23,10 @@ import androidx.compose.material3.Tab
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavHostController
@@ -69,8 +69,12 @@ fun LibraryScreen(
navController: NavHostController,
viewModel: LibraryViewModel = hiltViewModel(),
) {
var selectedTab by remember { mutableIntStateOf(0) }
val tabs = LIBRARY_TABS
// HorizontalPager owns the active-tab state — the TabRow reads
// pagerState.currentPage and animateScrollToPage drives it on tap,
// so swipe and tap stay in sync.
val pagerState = rememberPagerState(pageCount = { tabs.size })
val scope = rememberCoroutineScope()
Scaffold(
modifier = Modifier.fillMaxSize(),
@@ -91,13 +95,13 @@ fun LibraryScreen(
},
)
PrimaryScrollableTabRow(
selectedTabIndex = selectedTab,
selectedTabIndex = pagerState.currentPage,
edgePadding = 16.dp,
) {
tabs.forEachIndexed { index, label ->
Tab(
selected = selectedTab == index,
onClick = { selectedTab = index },
selected = pagerState.currentPage == index,
onClick = { scope.launch { pagerState.animateScrollToPage(index) } },
text = { Text(label) },
)
}
@@ -105,8 +109,11 @@ fun LibraryScreen(
}
},
) { inner ->
Box(modifier = Modifier.fillMaxSize().padding(inner)) {
when (selectedTab) {
HorizontalPager(
state = pagerState,
modifier = Modifier.fillMaxSize().padding(inner),
) { page ->
when (page) {
TAB_ARTISTS -> ArtistsTab(viewModel = viewModel, navController = navController)
TAB_ALBUMS -> AlbumsTab(viewModel = viewModel, navController = navController)
TAB_HISTORY -> HistoryTab(