address the review: blank source name, synchronous title reset, shared scrim

- QueueRepository.sourceName returns null for a blank album/playlist name so the now
  playing title fallback applies instead of rendering empty
- resolve the now playing source title with remember + LaunchedEffect instead of
  produceState so it resets synchronously when the queue context changes
- extract the repeated status bar scrim into Modifier.statusBarScrim() and use it in
  AppRoot, LibraryScreen and HeroDetailScaffold
This commit is contained in:
austinried
2026-10-04 00:29:58 +00:00
parent b16fe0f8d6
commit bce074ecdc
6 changed files with 51 additions and 38 deletions
@@ -127,9 +127,25 @@ class QueueRepository(
): String? { ): String? {
val library = db.libraryDao() val library = db.libraryDao()
return when (kind) { return when (kind) {
QueueKind.Album -> library.album(sourceId, refId).first()?.name QueueKind.Album -> {
QueueKind.Playlist -> library.playlist(sourceId, refId).first()?.name library
QueueKind.Songs, QueueKind.Song -> null .album(sourceId, refId)
.first()
?.name
?.takeIf { it.isNotBlank() }
}
QueueKind.Playlist -> {
library
.playlist(sourceId, refId)
.first()
?.name
?.takeIf { it.isNotBlank() }
}
QueueKind.Songs, QueueKind.Song -> {
null
}
} }
} }
@@ -15,7 +15,6 @@ import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.slideOutVertically import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.draggable import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.gestures.rememberDraggableState import androidx.compose.foundation.gestures.rememberDraggableState
@@ -25,12 +24,9 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imeAnimationTarget import androidx.compose.foundation.layout.imeAnimationTarget
import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.statusBars
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@@ -44,8 +40,6 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInRoot import androidx.compose.ui.layout.positionInRoot
@@ -63,6 +57,7 @@ import androidx.navigation.navArgument
import com.subtracks.data.repo.SourceRepository import com.subtracks.data.repo.SourceRepository
import com.subtracks.playback.PlaybackController import com.subtracks.playback.PlaybackController
import com.subtracks.ui.components.LoadingState import com.subtracks.ui.components.LoadingState
import com.subtracks.ui.components.statusBarScrim
import com.subtracks.ui.library.AlbumDetailRoute import com.subtracks.ui.library.AlbumDetailRoute
import com.subtracks.ui.library.ArtistDetailRoute import com.subtracks.ui.library.ArtistDetailRoute
import com.subtracks.ui.library.LibraryRoute import com.subtracks.ui.library.LibraryRoute
@@ -173,7 +168,6 @@ private fun MainNavigation() {
} }
val density = LocalDensity.current val density = LocalDensity.current
val navBarInset = with(density) { WindowInsets.navigationBars.getBottom(this).toDp() } val navBarInset = with(density) { WindowInsets.navigationBars.getBottom(this).toDp() }
val statusBarInset = with(density) { WindowInsets.statusBars.getTop(this).toDp() }
val keyboardUp = WindowInsets.imeAnimationTarget.getBottom(density) > 0 val keyboardUp = WindowInsets.imeAnimationTarget.getBottom(density) > 0
val showMiniPlayer = playerVisible && !keyboardUp val showMiniPlayer = playerVisible && !keyboardUp
val bottomInset by animateDpAsState( val bottomInset by animateDpAsState(
@@ -349,15 +343,10 @@ private fun MainNavigation() {
modifier = modifier =
Modifier Modifier
.align(Alignment.TopCenter) .align(Alignment.TopCenter)
.fillMaxWidth() .statusBarScrim()
.height(statusBarInset + 8.dp)
.graphicsLayer { .graphicsLayer {
alpha = ((nowPlayingProgress - SCRIM_FADE_START) / (1f - SCRIM_FADE_START)).coerceIn(0f, 1f) alpha = ((nowPlayingProgress - SCRIM_FADE_START) / (1f - SCRIM_FADE_START)).coerceIn(0f, 1f)
}.background( },
Brush.verticalGradient(
listOf(Color.Black.copy(alpha = 0.55f), Color.Transparent),
),
),
) )
} }
@@ -9,9 +9,13 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
@@ -26,7 +30,10 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow import androidx.compose.ui.draw.shadow
import androidx.compose.ui.geometry.Size import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -259,3 +266,16 @@ fun FilteredEmptyState(
} }
} }
} }
@Composable
fun Modifier.statusBarScrim(): Modifier {
val top = with(LocalDensity.current) { WindowInsets.statusBars.getTop(this).toDp() }
return this
.fillMaxWidth()
.height(top + 8.dp)
.background(
Brush.verticalGradient(
listOf(Color.Black.copy(alpha = 0.55f), Color.Transparent),
),
)
}
@@ -49,7 +49,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onGloballyPositioned
@@ -153,13 +152,8 @@ fun HeroDetailScaffold(
Box( Box(
Modifier Modifier
.align(Alignment.TopStart) .align(Alignment.TopStart)
.fillMaxWidth() .statusBarScrim()
.height(statusBarDp + 8.dp) .graphicsLayer { alpha = 1f - barFraction },
.background(
Brush.verticalGradient(
listOf(Color.Black.copy(alpha = 0.55f), Color.Transparent),
),
).graphicsLayer { alpha = 1f - barFraction },
) )
TopAppBar( TopAppBar(
@@ -79,7 +79,6 @@ import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.ClipOp import androidx.compose.ui.graphics.ClipOp
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.Path
@@ -115,6 +114,7 @@ import com.subtracks.data.prefs.PlaylistSort
import com.subtracks.data.prefs.SongSort import com.subtracks.data.prefs.SongSort
import com.subtracks.data.prefs.StarredFilter import com.subtracks.data.prefs.StarredFilter
import com.subtracks.playback.PlaybackController import com.subtracks.playback.PlaybackController
import com.subtracks.ui.components.statusBarScrim
import com.subtracks.ui.theme.ArtworkColors import com.subtracks.ui.theme.ArtworkColors
import com.subtracks.ui.theme.librarySurfaceColor import com.subtracks.ui.theme.librarySurfaceColor
import com.subtracks.ui.theme.playerSurfaceColor import com.subtracks.ui.theme.playerSurfaceColor
@@ -430,13 +430,7 @@ fun LibraryScreen(
modifier = modifier =
Modifier Modifier
.align(Alignment.TopStart) .align(Alignment.TopStart)
.fillMaxWidth() .statusBarScrim(),
.height(statusBarTop + 8.dp)
.background(
Brush.verticalGradient(
listOf(Color.Black.copy(alpha = 0.55f), Color.Transparent),
),
),
) )
if (!searchActive) { if (!searchActive) {
@@ -43,7 +43,6 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -91,9 +90,10 @@ fun NowPlayingRoute(
} else { } else {
state.item?.album?.takeIf { it.isNotBlank() } ?: "Library" state.item?.album?.takeIf { it.isNotBlank() } ?: "Library"
} }
val sourceTitle by produceState<String>(initialValue = fallbackTitle, queueContext) { val sourceTitle = remember(queueContext, fallbackTitle) { mutableStateOf(fallbackTitle) }
value = fallbackTitle LaunchedEffect(queueContext, fallbackTitle) {
value = controller.sourceTitle(queueContext) ?: fallbackTitle sourceTitle.value = fallbackTitle
sourceTitle.value = controller.sourceTitle(queueContext)?.takeIf { it.isNotBlank() } ?: fallbackTitle
} }
LaunchedEffect(state.item?.id, state.hasNext) { LaunchedEffect(state.item?.id, state.hasNext) {
if (!state.hasNext) return@LaunchedEffect if (!state.hasNext) return@LaunchedEffect
@@ -102,7 +102,7 @@ fun NowPlayingRoute(
} }
NowPlayingScreen( NowPlayingScreen(
state = state, state = state,
title = sourceTitle, title = sourceTitle.value,
coverArt = art, coverArt = art,
thumbnailRef = thumbnail, thumbnailRef = thumbnail,
artwork = rememberArtworkColors(thumbnail ?: art), artwork = rememberArtworkColors(thumbnail ?: art),