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? {
val library = db.libraryDao()
return when (kind) {
QueueKind.Album -> library.album(sourceId, refId).first()?.name
QueueKind.Playlist -> library.playlist(sourceId, refId).first()?.name
QueueKind.Songs, QueueKind.Song -> null
QueueKind.Album -> {
library
.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.slideOutHorizontally
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.draggable
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.consumeWindowInsets
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.imePadding
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.statusBars
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
@@ -44,8 +40,6 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
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.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInRoot
@@ -63,6 +57,7 @@ import androidx.navigation.navArgument
import com.subtracks.data.repo.SourceRepository
import com.subtracks.playback.PlaybackController
import com.subtracks.ui.components.LoadingState
import com.subtracks.ui.components.statusBarScrim
import com.subtracks.ui.library.AlbumDetailRoute
import com.subtracks.ui.library.ArtistDetailRoute
import com.subtracks.ui.library.LibraryRoute
@@ -173,7 +168,6 @@ private fun MainNavigation() {
}
val density = LocalDensity.current
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 showMiniPlayer = playerVisible && !keyboardUp
val bottomInset by animateDpAsState(
@@ -349,15 +343,10 @@ private fun MainNavigation() {
modifier =
Modifier
.align(Alignment.TopCenter)
.fillMaxWidth()
.height(statusBarInset + 8.dp)
.statusBarScrim()
.graphicsLayer {
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.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
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.size
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
@@ -26,7 +30,10 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
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.platform.LocalDensity
import androidx.compose.ui.text.style.TextAlign
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.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.onGloballyPositioned
@@ -153,13 +152,8 @@ fun HeroDetailScaffold(
Box(
Modifier
.align(Alignment.TopStart)
.fillMaxWidth()
.height(statusBarDp + 8.dp)
.background(
Brush.verticalGradient(
listOf(Color.Black.copy(alpha = 0.55f), Color.Transparent),
),
).graphicsLayer { alpha = 1f - barFraction },
.statusBarScrim()
.graphicsLayer { alpha = 1f - barFraction },
)
TopAppBar(
@@ -79,7 +79,6 @@ import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.ClipOp
import androidx.compose.ui.graphics.Color
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.StarredFilter
import com.subtracks.playback.PlaybackController
import com.subtracks.ui.components.statusBarScrim
import com.subtracks.ui.theme.ArtworkColors
import com.subtracks.ui.theme.librarySurfaceColor
import com.subtracks.ui.theme.playerSurfaceColor
@@ -430,13 +430,7 @@ fun LibraryScreen(
modifier =
Modifier
.align(Alignment.TopStart)
.fillMaxWidth()
.height(statusBarTop + 8.dp)
.background(
Brush.verticalGradient(
listOf(Color.Black.copy(alpha = 0.55f), Color.Transparent),
),
),
.statusBarScrim(),
)
if (!searchActive) {
@@ -43,7 +43,6 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
@@ -91,9 +90,10 @@ fun NowPlayingRoute(
} else {
state.item?.album?.takeIf { it.isNotBlank() } ?: "Library"
}
val sourceTitle by produceState<String>(initialValue = fallbackTitle, queueContext) {
value = fallbackTitle
value = controller.sourceTitle(queueContext) ?: fallbackTitle
val sourceTitle = remember(queueContext, fallbackTitle) { mutableStateOf(fallbackTitle) }
LaunchedEffect(queueContext, fallbackTitle) {
sourceTitle.value = fallbackTitle
sourceTitle.value = controller.sourceTitle(queueContext)?.takeIf { it.isNotBlank() } ?: fallbackTitle
}
LaunchedEffect(state.item?.id, state.hasNext) {
if (!state.hasNext) return@LaunchedEffect
@@ -102,7 +102,7 @@ fun NowPlayingRoute(
}
NowPlayingScreen(
state = state,
title = sourceTitle,
title = sourceTitle.value,
coverArt = art,
thumbnailRef = thumbnail,
artwork = rememberArtworkColors(thumbnail ?: art),