Skip to content

Commit

Permalink
feat: Replaced song cards with compact song cards
Browse files Browse the repository at this point in the history
feat: Added Settings

This commit introduces compact cards to replace the previous vertical cards for displaying songs.
The size of the cards can be configured in the Settings.
Additionally, this change adds conditional marquee text, to give the user the option to disable the marque text animation.
  • Loading branch information
BobbyESP committed Dec 1, 2024
1 parent 6fda8d4 commit a1b3ac3
Show file tree
Hide file tree
Showing 17 changed files with 443 additions and 117 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.bobbyesp.metadator.presentation.components.image.AsyncImage
import com.bobbyesp.metadator.presentation.components.text.ConditionedMarqueeText
import com.bobbyesp.metadator.presentation.theme.MetadatorTheme
import com.bobbyesp.ui.components.text.MarqueeText
import com.bobbyesp.utilities.Time
Expand Down Expand Up @@ -50,13 +51,13 @@ fun HorizontalSongCard(
.padding(vertical = 8.dp, horizontal = 6.dp)
.weight(1f)
) {
MarqueeText(
ConditionedMarqueeText(
text = song.title,
style = MaterialTheme.typography.bodyLarge,
fontWeight = FontWeight.Bold,
fontSize = 15.sp
)
MarqueeText(
ConditionedMarqueeText(
text = song.artist,
style = MaterialTheme.typography.bodyMedium.copy(
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.bobbyesp.metadator.presentation.components.image.AsyncImage
import com.bobbyesp.metadator.presentation.components.text.ConditionedMarqueeText
import com.bobbyesp.metadator.presentation.theme.MetadatorTheme
import com.bobbyesp.ui.components.text.MarqueeText
import com.bobbyesp.utilities.model.Song
Expand All @@ -41,13 +42,13 @@ fun VerticalSongCard(
Column(
horizontalAlignment = Alignment.Start, modifier = Modifier.padding(8.dp)
) {
MarqueeText(
ConditionedMarqueeText(
text = song.title,
style = MaterialTheme.typography.bodyLarge,
fontWeight = FontWeight.Bold,
fontSize = 15.sp
)
MarqueeText(
ConditionedMarqueeText(
text = song.artist,
style = MaterialTheme.typography.bodyMedium.copy(
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.bobbyesp.metadator.presentation.components.cards.songs.compact

import androidx.compose.foundation.shape.CornerBasedShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp

enum class CompactCardSize(val value: Dp) {
SMALL(96.dp),
MEDIUM(120.dp),
LARGE(144.dp),
EXTRA_LARGE(168.dp);

companion object {

fun Int.toCompactCardSize(): CompactCardSize = CompactCardSize.entries.first { it.ordinal == this }

@Composable
fun CompactCardSize.toShape(): CornerBasedShape = when(this) {
SMALL -> MaterialTheme.shapes.small
MEDIUM -> MaterialTheme.shapes.medium
LARGE -> MaterialTheme.shapes.large
EXTRA_LARGE -> MaterialTheme.shapes.extraLarge
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package com.bobbyesp.metadator.presentation.components.cards.songs.compact

import android.net.Uri
import android.util.Log
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
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.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.bobbyesp.metadator.presentation.components.cards.songs.compact.CompactCardSize.Companion.toShape
import com.bobbyesp.metadator.presentation.components.image.AsyncImage
import com.bobbyesp.metadator.presentation.components.text.ConditionedMarqueeText

@Composable
fun CompactSongCard(
modifier: Modifier = Modifier,
name: String,
artists: String,
artworkUri: Uri? = null,
listIndex: Int? = null,
shadow: Dp? = 4.dp,
size: CompactCardSize = CompactCardSize.LARGE,
shape: Shape? = MaterialTheme.shapes.large,
onClick: () -> Unit
) {
val cardSize by remember(size) {
mutableStateOf(size.value)
}

val formalizedShape = shape ?: size.toShape()

Box(
modifier = modifier
.shadow(
elevation = shadow ?: 0.dp,
shape = formalizedShape
)
.clip(formalizedShape)
.size(cardSize)
.clickable(onClick = onClick)

) {
AsyncImage(
modifier = Modifier.fillMaxSize(),
imageModel = artworkUri,
)

listIndex?.let {
Text(
text = "$it.",
style = MaterialTheme.typography.bodySmall,
color = Color.White.copy(alpha = 0.8f),
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(8.dp)
.align(Alignment.TopEnd)
)
}
Column(
modifier = Modifier
.background(
brush = Brush.verticalGradient(
colors = listOf(
Color.Transparent,
MaterialTheme.colorScheme.scrim
),
startY = 0f,
endY = 500f
),
alpha = 0.6f
)
.fillMaxSize()
.padding(horizontal = 8.dp, vertical = 6.dp),
verticalArrangement = Arrangement.Bottom,
horizontalAlignment = Alignment.Start
) {
ConditionedMarqueeText(
text = name,
style = MaterialTheme.typography.titleSmall,
color = Color.White,
overflow = TextOverflow.Ellipsis,
maxLines = 1
)

if(artists.isNotEmpty()) {
ConditionedMarqueeText(
text = artists,
style = MaterialTheme.typography.bodySmall,
color = Color.White.copy(alpha = 0.6f),
overflow = TextOverflow.Ellipsis,
maxLines = 1
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.compose.ui.unit.dp
import com.adamratzman.spotify.models.Track
import com.bobbyesp.metadator.ext.formatArtists
import com.bobbyesp.metadator.presentation.components.image.AsyncImage
import com.bobbyesp.metadator.presentation.components.text.ConditionedMarqueeText
import com.bobbyesp.ui.components.text.MarqueeText

@OptIn(ExperimentalFoundationApi::class)
Expand Down Expand Up @@ -80,12 +81,12 @@ fun SpotifyHorizontalSongCard(
.padding(8.dp)
.weight(1f)
) {
MarqueeText(
ConditionedMarqueeText(
text = track.name,
style = MaterialTheme.typography.bodyLarge,
fontWeight = FontWeight.Bold,
)
MarqueeText(
ConditionedMarqueeText(
text = track.artists.formatArtists(),
style = MaterialTheme.typography.bodyMedium.copy(
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package com.bobbyesp.metadator.presentation.components.text

import androidx.compose.animation.core.Easing
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.TextUnit
import com.bobbyesp.ui.components.text.MarqueeText
import com.bobbyesp.ui.components.text.MarqueeTextGradientOptions
import com.bobbyesp.utilities.preferences.PreferencesKeys.MARQUEE_TEXT
import com.bobbyesp.utilities.preferences.booleanState

@Composable
fun ConditionedMarqueeText(
text: String,
modifier: Modifier = Modifier,
textModifier: Modifier = Modifier,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
maxLines: Int = 1,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = LocalTextStyle.current.plus(TextStyle()),
sideGradient: MarqueeTextGradientOptions = MarqueeTextGradientOptions(),
customEasing: Easing? = null,
animationDuration: Float = 4000f,
delayBetweenAnimations: Long = 500L
) {
val useMarqueeText = MARQUEE_TEXT.booleanState

if(useMarqueeText.value) {
MarqueeText(
text,
modifier,
textModifier,
color,
fontSize,
fontStyle,
fontWeight,
fontFamily,
letterSpacing,
textDecoration,
textAlign,
lineHeight,
maxLines,
overflow,
softWrap,
onTextLayout,
style,
sideGradient,
customEasing,
animationDuration,
delayBetweenAnimations
)
} else {
Text(
text = text,
modifier = textModifier,
color = color,
fontSize = fontSize,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
maxLines = maxLines,
overflow = overflow,
softWrap = softWrap,
onTextLayout = onTextLayout,
style = style
)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.bobbyesp.metadator.presentation.pages

import androidx.compose.animation.Crossfade
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
Expand All @@ -15,17 +16,24 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.bobbyesp.metadator.R
import com.bobbyesp.metadator.presentation.components.cards.songs.HorizontalSongCard
import com.bobbyesp.metadator.presentation.components.cards.songs.VerticalSongCard
import com.bobbyesp.metadator.presentation.components.cards.songs.compact.CompactCardSize
import com.bobbyesp.metadator.presentation.components.cards.songs.compact.CompactCardSize.Companion.toCompactCardSize
import com.bobbyesp.metadator.presentation.components.cards.songs.compact.CompactSongCard
import com.bobbyesp.metadator.presentation.components.others.status.EmptyMediaStore
import com.bobbyesp.metadator.presentation.pages.home.LayoutType
import com.bobbyesp.ui.common.pages.ErrorPage
import com.bobbyesp.ui.common.pages.LoadingPage
import com.bobbyesp.utilities.model.Song
import com.bobbyesp.utilities.preferences.PreferencesKeys.SONG_CARD_SIZE
import com.bobbyesp.utilities.preferences.intState
import com.bobbyesp.utilities.states.ResourceState
import my.nanihadesuka.compose.LazyColumnScrollbar
import my.nanihadesuka.compose.LazyVerticalGridScrollbar
Expand All @@ -39,10 +47,12 @@ fun MediaStorePage(
lazyGridState: LazyGridState,
lazyListState: LazyListState,
desiredLayout: LayoutType,
compactCardSize: CompactCardSize,
onReloadMediaStore: () -> Unit,
onItemClicked: (Song) -> Unit
) {
val songsList = songs.value

Box(
modifier = modifier.fillMaxSize()
) {
Expand All @@ -58,7 +68,7 @@ fun MediaStorePage(
) { onReloadMediaStore() }

is ResourceState.Success -> {
val dataSongsList = songsList.data!!
val dataSongsList = songsList.data ?: throw IllegalStateException("Data is null")
if (dataSongsList.isEmpty()) {
EmptyMediaStore(
modifier = Modifier.fillMaxSize()
Expand Down Expand Up @@ -88,11 +98,16 @@ fun MediaStorePage(
key = { index -> dataSongsList[index].id },
contentType = { _ -> "songItem" }) { index ->
val song = dataSongsList[index]
VerticalSongCard(
song = song,
modifier = Modifier.animateItem(
fadeInSpec = null, fadeOutSpec = null
),

CompactSongCard(
modifier = Modifier
.animateItem(
fadeInSpec = null, fadeOutSpec = null
),
size = compactCardSize,
name = song.title,
artists = song.artist,
artworkUri = song.artworkPath,
onClick = {
onItemClicked(song)
})
Expand Down
Loading

0 comments on commit a1b3ac3

Please sign in to comment.