mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-10 15:11:58 +01:00
make the now playing art and artist names tap targets
Tapping the cover art on now playing opens its album and tapping the artist name opens the artist, matching the context menu actions; the album detail subtitle does the same for its artist. Both are inert when the queue item has no album/artist id. Artist detail's album cells also stop drawing the letter placeholder underneath: with a thumbnail underlay and the full image on top, the letter only showed because the default `showPlaceholder = true` was left on, so a cold cell flashed a letter before the art. They now match the hero's `showPlaceholder = art == null`.
This commit is contained in:
@@ -201,6 +201,7 @@ fun HeroHeader(
|
||||
controlsModifier: Modifier = Modifier,
|
||||
thumbnailRef: CoverArtRef? = null,
|
||||
comment: String? = null,
|
||||
onSubtitleClick: (() -> Unit)? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
@@ -235,6 +236,7 @@ fun HeroHeader(
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = if (onSubtitleClick != null) Modifier.clickable { onSubtitleClick() } else Modifier,
|
||||
)
|
||||
}
|
||||
if (!comment.isNullOrBlank()) {
|
||||
|
||||
@@ -80,6 +80,11 @@ fun AlbumDetailRoute(
|
||||
onShuffle = viewModel::shuffle,
|
||||
onPlay = viewModel::playAll,
|
||||
onMore = { album?.let { contextMenuHost?.show(MenuTarget.Album(it, viewModel.coverArt(it.coverArt, true)), actions) } },
|
||||
onArtistClick =
|
||||
album
|
||||
?.takeIf { !it.albumArtist.isNullOrBlank() }
|
||||
?.let { it.artistId }
|
||||
?.let { id -> { onViewArtist(id) } },
|
||||
playingSongId = playback.item?.id.takeIf { context?.kind == QueueKind.Album && context.refId == albumId },
|
||||
)
|
||||
}
|
||||
@@ -98,6 +103,7 @@ fun AlbumDetailScreen(
|
||||
onPlay: () -> Unit = { onSongClick(0) },
|
||||
onDownload: () -> Unit = {},
|
||||
onMore: () -> Unit = {},
|
||||
onArtistClick: (() -> Unit)? = null,
|
||||
playingSongId: String? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
@@ -124,15 +130,17 @@ fun AlbumDetailScreen(
|
||||
title = album?.name.orEmpty(),
|
||||
onBack = onBack,
|
||||
header = { controlsModifier, topInset ->
|
||||
val artistName = album?.albumArtist?.takeIf { it.isNotBlank() }
|
||||
HeroHeader(
|
||||
art = coverArt(album?.coverArt, false),
|
||||
thumbnailRef = coverArt(album?.coverArt, true),
|
||||
name = album?.name.orEmpty(),
|
||||
subtitle =
|
||||
listOfNotNull(
|
||||
album?.albumArtist?.takeIf { it.isNotBlank() },
|
||||
artistName,
|
||||
album?.year?.takeIf { it > 0 }?.toString(),
|
||||
).joinToString(" $DOT "),
|
||||
onSubtitleClick = onArtistClick,
|
||||
hasSongs = songs.isNotEmpty(),
|
||||
onPlay = onPlay,
|
||||
onShuffle = onShuffle,
|
||||
|
||||
@@ -270,7 +270,9 @@ fun ArtistDetailScreen(
|
||||
}
|
||||
}
|
||||
itemsIndexed(albums, key = { _, album -> album.id }) { index, album ->
|
||||
PrefetchArtworkSeeds(coverArt(album.coverArt, true))
|
||||
val art = coverArt(album.coverArt, false)
|
||||
val thumbnail = coverArt(album.coverArt, true)
|
||||
PrefetchArtworkSeeds(thumbnail)
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
@@ -279,14 +281,15 @@ fun ArtistDetailScreen(
|
||||
end = if (index % 2 == 1) 16.dp else 0.dp,
|
||||
).combinedClickable(
|
||||
onClick = { onAlbumClick(album) },
|
||||
onLongClick = { onAlbumLongClick(MenuTarget.Album(album, coverArt(album.coverArt, true))) },
|
||||
onLongClick = { onAlbumLongClick(MenuTarget.Album(album, thumbnail)) },
|
||||
).onSizeChanged { albumHeightPx = it.height.toFloat() },
|
||||
) {
|
||||
Box {
|
||||
CoverArt(
|
||||
ref = coverArt(album.coverArt, false),
|
||||
ref = art,
|
||||
name = album.name,
|
||||
thumbnailRef = coverArt(album.coverArt, true),
|
||||
thumbnailRef = thumbnail,
|
||||
showPlaceholder = art == null,
|
||||
modifier = Modifier.fillMaxWidth().aspectRatio(1f).clip(RoundedCornerShape(2.dp)),
|
||||
)
|
||||
StarredBadge(
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.subtracks.ui.playback
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.basicMarquee
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -50,6 +51,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.PlatformTextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.LineHeightStyle
|
||||
@@ -80,6 +82,8 @@ import com.subtracks.ui.theme.HeroGradient
|
||||
import com.subtracks.ui.theme.rememberArtworkColors
|
||||
import org.koin.compose.koinInject
|
||||
|
||||
const val NOW_PLAYING_COVER_TAG = "now-playing-cover"
|
||||
|
||||
@Composable
|
||||
fun NowPlayingRoute(
|
||||
onBack: () -> Unit,
|
||||
@@ -139,6 +143,8 @@ fun NowPlayingRoute(
|
||||
onShuffle = controller::toggleShuffle,
|
||||
onRepeat = controller::cycleRepeat,
|
||||
onMore = { song?.let { contextMenuHost?.show(MenuTarget.Song(it, thumbnail), actions) } },
|
||||
onAlbumClick = song?.albumId?.let { id -> { onViewAlbum(id) } },
|
||||
onArtistClick = song?.artistId?.let { id -> { onViewArtist(id) } },
|
||||
onSeek = controller::seekTo,
|
||||
modifier = modifier,
|
||||
)
|
||||
@@ -182,6 +188,8 @@ fun NowPlayingScreen(
|
||||
onShuffle: () -> Unit = {},
|
||||
onRepeat: () -> Unit = {},
|
||||
onMore: () -> Unit = {},
|
||||
onAlbumClick: (() -> Unit)? = null,
|
||||
onArtistClick: (() -> Unit)? = null,
|
||||
onSeek: (Long) -> Unit,
|
||||
thumbnailRef: CoverArtRef? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
@@ -247,7 +255,13 @@ fun NowPlayingScreen(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.weight(1f).fillMaxWidth().padding(top = 2.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.fillMaxWidth()
|
||||
.padding(top = 2.dp)
|
||||
.clickable(enabled = onAlbumClick != null) { onAlbumClick?.invoke() }
|
||||
.testTag(NOW_PLAYING_COVER_TAG),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
CoverArt(
|
||||
@@ -286,7 +300,13 @@ fun NowPlayingScreen(
|
||||
modifier = Modifier.basicMarquee(),
|
||||
)
|
||||
}
|
||||
Box(Modifier.height(subtitleHeight), contentAlignment = Alignment.Center) {
|
||||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
.height(subtitleHeight)
|
||||
.clickable(enabled = onArtistClick != null) { onArtistClick?.invoke() },
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
text = state.item?.artist.orEmpty(),
|
||||
style = subtitleStyle,
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.subtracks.ui.playback
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.junit4.v2.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.subtracks.data.model.CoverArtRef
|
||||
import com.subtracks.playback.PlaybackState
|
||||
import com.subtracks.playback.QueueItem
|
||||
import com.subtracks.ui.theme.SubtracksTheme
|
||||
import com.subtracks.ui.theme.artworkColorsFromSeed
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.annotation.GraphicsMode
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@GraphicsMode(GraphicsMode.Mode.NATIVE)
|
||||
class NowPlayingTapsTest {
|
||||
@get:Rule
|
||||
val composeRule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
private val state =
|
||||
PlaybackState(
|
||||
item =
|
||||
QueueItem(
|
||||
id = "s-eiirp",
|
||||
title = "Everything In Its Right Place",
|
||||
artist = "Radiohead",
|
||||
album = "Kid A",
|
||||
coverArtId = "art-al-kid-a",
|
||||
),
|
||||
isPlaying = true,
|
||||
durationMs = 251_000,
|
||||
hasNext = true,
|
||||
hasPrevious = false,
|
||||
)
|
||||
|
||||
private fun render(
|
||||
onAlbumClick: () -> Unit = {},
|
||||
onArtistClick: () -> Unit = {},
|
||||
) {
|
||||
composeRule.setContent {
|
||||
SubtracksTheme {
|
||||
NowPlayingScreen(
|
||||
state = state,
|
||||
positionMs = 62_000,
|
||||
title = "Kid A",
|
||||
coverArt = CoverArtRef("art-al-kid-a", "test:art-al-kid-a"),
|
||||
artwork = artworkColorsFromSeed(android.graphics.Color.rgb(120, 80, 200)),
|
||||
onBack = {},
|
||||
onQueue = {},
|
||||
onPlayPause = {},
|
||||
onNext = {},
|
||||
onPrevious = {},
|
||||
onAlbumClick = onAlbumClick,
|
||||
onArtistClick = onArtistClick,
|
||||
onSeek = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tappingTheCoverArtOpensTheAlbum() {
|
||||
var opened = false
|
||||
render(onAlbumClick = { opened = true })
|
||||
|
||||
composeRule.onNodeWithTag(NOW_PLAYING_COVER_TAG).performClick()
|
||||
|
||||
assertTrue("the cover art should open the album", opened)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tappingTheArtistNameOpensTheArtist() {
|
||||
var opened = false
|
||||
render(onArtistClick = { opened = true })
|
||||
|
||||
composeRule.onNodeWithText("Radiohead").performClick()
|
||||
|
||||
assertTrue("the artist name should open the artist", opened)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun theTapTargetsStayInertWithoutADestination() {
|
||||
var opened = false
|
||||
render()
|
||||
|
||||
composeRule.onNodeWithTag(NOW_PLAYING_COVER_TAG).performClick()
|
||||
composeRule.onNodeWithText("Radiohead").performClick()
|
||||
|
||||
assertFalse(opened)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user