fix: folder tab long-press via Initial-pass pointerInput watcher + shared Handler timer; taps still select (verified on-device: 8-step gesture suite)
This commit is contained in:
parent
3f84249c82
commit
a99d4c43f4
1 changed files with 48 additions and 11 deletions
|
|
@ -4,7 +4,8 @@ import androidx.activity.compose.rememberLauncherForActivityResult
|
|||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.gestures.awaitEachGesture
|
||||
import androidx.compose.foundation.gestures.awaitFirstDown
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -53,17 +54,20 @@ import androidx.compose.material3.TextFieldDefaults
|
|||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.pointer.PointerEventPass
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -178,6 +182,18 @@ fun LibraryScreen(
|
|||
Icon(Icons.Default.Shuffle, contentDescription = "Shuffle play")
|
||||
}
|
||||
}
|
||||
// Reliable delete path for the selected folder (besides long-press).
|
||||
if (uiState.selectedPlaylistId != null) {
|
||||
allPlaylists.firstOrNull { it.id == uiState.selectedPlaylistId }?.let { selected ->
|
||||
IconButton(onClick = { playlistToDelete = selected }) {
|
||||
Icon(
|
||||
Icons.Default.Delete,
|
||||
contentDescription = "Delete folder",
|
||||
tint = PremiumTextSecondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.background,
|
||||
|
|
@ -208,17 +224,38 @@ fun LibraryScreen(
|
|||
}
|
||||
},
|
||||
) {
|
||||
// One shared long-press timer for the folder tabs. The
|
||||
// per-Tab watcher (pointerInput on the Initial pass) arms
|
||||
// it on DOWN and disarms on UP/CANCEL/move; the Tab's own
|
||||
// selectable keeps handling taps normally on the Main pass.
|
||||
val tabLpHandler = android.os.Handler(android.os.Looper.getMainLooper())
|
||||
DisposableEffect(Unit) {
|
||||
onDispose { tabLpHandler.removeCallbacksAndMessages(null) }
|
||||
}
|
||||
tabLabels.forEachIndexed { index, label ->
|
||||
Tab(
|
||||
// Long-press detected at the pointer level: putting
|
||||
// combinedClickable here would be masked by Tab's own
|
||||
// selectable semantics and break selection entirely.
|
||||
modifier = Modifier.pointerInput(index) {
|
||||
detectTapGestures(
|
||||
onLongPress = {
|
||||
if (index > 0) playlistToDelete = filteredPlaylists[index - 1]
|
||||
},
|
||||
modifier = if (index == 0) Modifier else Modifier.pointerInput(index) {
|
||||
awaitEachGesture {
|
||||
val down = awaitFirstDown(
|
||||
requireUnconsumed = false,
|
||||
pass = PointerEventPass.Initial,
|
||||
)
|
||||
val playlist = filteredPlaylists[index - 1]
|
||||
val slop = viewConfiguration.touchSlop
|
||||
val timer = Runnable { playlistToDelete = playlist }
|
||||
tabLpHandler.removeCallbacks(timer)
|
||||
tabLpHandler.postDelayed(
|
||||
timer,
|
||||
viewConfiguration.longPressTimeoutMillis,
|
||||
)
|
||||
while (true) {
|
||||
val event = awaitPointerEvent(PointerEventPass.Initial)
|
||||
val change = event.changes.firstOrNull { it.id == down.id }
|
||||
if (change == null || !change.pressed) break
|
||||
if ((change.position - down.position).getDistance() > slop) break
|
||||
}
|
||||
tabLpHandler.removeCallbacks(timer)
|
||||
}
|
||||
},
|
||||
selected = selectedTabIndex == index,
|
||||
onClick = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue