feat(android): LikedTab inline heart + Settings sign-out confirmation
Two more audit v3 cleanup items: §4.11 — LikedTab track rows lacked an inline LikeButton. Users had to open the kebab menu to unlike a track — two taps for what should be one. Added LikeButton(liked = true) next to the TrackActionsButton; tap unlikes via LikedTabViewModel.unlikeTrack which routes through LikesRepository.toggleLike (and therefore the MutationQueue, so offline taps replay). §4.23 — SettingsScreen Sign Out tapped immediately wiped local state with no confirmation. Now: AlertDialog with "Sign out of this server? Your downloaded music and settings on this device will be removed." and an errorContainer-tinted confirm button. Cancel by default for accidental taps. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,7 @@ import com.fabledsword.minstrel.nav.ArtistDetail
|
||||
import com.fabledsword.minstrel.player.PlayerController
|
||||
import com.fabledsword.minstrel.shared.widgets.EmptyState
|
||||
import com.fabledsword.minstrel.shared.widgets.HorizontalScrollRow
|
||||
import com.fabledsword.minstrel.shared.widgets.LikeButton
|
||||
import com.fabledsword.minstrel.shared.widgets.LoadingCentered
|
||||
import com.fabledsword.minstrel.shared.widgets.PullToRefreshScaffold
|
||||
import com.fabledsword.minstrel.shared.widgets.TrackCoverThumb
|
||||
@@ -88,6 +89,16 @@ class LikedTabViewModel @Inject constructor(
|
||||
runCatching { repository.refreshIds() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlike a track from inline heart-tap. The LikesRepository write
|
||||
* routes through the MutationQueue so offline taps replay later.
|
||||
*/
|
||||
fun unlikeTrack(trackId: String) {
|
||||
viewModelScope.launch {
|
||||
repository.toggleLike(LikesRepository.ENTITY_TRACK, trackId, liked = false)
|
||||
}
|
||||
}
|
||||
|
||||
val uiState: StateFlow<LikedTabUiState> = combine(
|
||||
repository.observeLikedArtists(),
|
||||
repository.observeLikedAlbums(),
|
||||
@@ -148,6 +159,7 @@ fun LikedTab(
|
||||
onTrackClick = { index ->
|
||||
viewModel.playTracks(s.sections.tracks, index)
|
||||
},
|
||||
onUnlike = viewModel::unlikeTrack,
|
||||
onNavigateToAlbum = { id -> navController.navigate(AlbumDetail(id)) },
|
||||
onNavigateToArtist = { id -> navController.navigate(ArtistDetail(id)) },
|
||||
)
|
||||
@@ -162,6 +174,7 @@ private fun LikedContent(
|
||||
onArtistClick: (String) -> Unit,
|
||||
onAlbumClick: (String) -> Unit,
|
||||
onTrackClick: (Int) -> Unit,
|
||||
onUnlike: (String) -> Unit,
|
||||
onNavigateToAlbum: (String) -> Unit,
|
||||
onNavigateToArtist: (String) -> Unit,
|
||||
) {
|
||||
@@ -202,6 +215,7 @@ private fun LikedContent(
|
||||
track = track,
|
||||
nowPlaying = track.id == playingTrackId,
|
||||
onClick = { onTrackClick(index) },
|
||||
onUnlike = { onUnlike(track.id) },
|
||||
onNavigateToAlbum = onNavigateToAlbum,
|
||||
onNavigateToArtist = onNavigateToArtist,
|
||||
)
|
||||
@@ -239,6 +253,7 @@ private fun LikedTrackRow(
|
||||
track: TrackRef,
|
||||
nowPlaying: Boolean,
|
||||
onClick: () -> Unit,
|
||||
onUnlike: () -> Unit,
|
||||
onNavigateToAlbum: (String) -> Unit,
|
||||
onNavigateToArtist: (String) -> Unit,
|
||||
) {
|
||||
@@ -272,6 +287,7 @@ private fun LikedTrackRow(
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
LikeButton(liked = true, onToggle = onUnlike)
|
||||
TrackActionsButton(
|
||||
track = track,
|
||||
onNavigateToAlbum = onNavigateToAlbum,
|
||||
|
||||
@@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
@@ -26,10 +27,14 @@ import androidx.compose.material3.SegmentedButton
|
||||
import androidx.compose.material3.SegmentedButtonDefaults
|
||||
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
@@ -61,6 +66,7 @@ fun SettingsScreen(
|
||||
) {
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
val themeMode by themeVm.themeMode.collectAsStateWithLifecycle()
|
||||
var showSignOutConfirm by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(state.signedOut) {
|
||||
if (state.signedOut) {
|
||||
@@ -118,9 +124,39 @@ fun SettingsScreen(
|
||||
AppearanceCard(themeMode = themeMode, onPick = themeVm::setThemeMode)
|
||||
StorageCard()
|
||||
AboutCard()
|
||||
SignOutButton(isSigningOut = state.isSigningOut, onSignOut = viewModel::signOut)
|
||||
SignOutButton(
|
||||
isSigningOut = state.isSigningOut,
|
||||
onSignOut = { showSignOutConfirm = true },
|
||||
)
|
||||
}
|
||||
}
|
||||
if (showSignOutConfirm) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showSignOutConfirm = false },
|
||||
title = { Text("Sign out?") },
|
||||
text = {
|
||||
Text(
|
||||
"Sign out of this server? Your downloaded music and " +
|
||||
"settings on this device will be removed.",
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
Button(
|
||||
onClick = {
|
||||
showSignOutConfirm = false
|
||||
viewModel.signOut()
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.errorContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onErrorContainer,
|
||||
),
|
||||
) { Text("Sign out") }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showSignOutConfirm = false }) { Text("Cancel") }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
Reference in New Issue
Block a user