From 59b6c61bb21d3a4e85c8efc872ed646a2112d791 Mon Sep 17 00:00:00 2001 From: vfsfitvnm Date: Wed, 6 Jul 2022 19:30:40 +0200 Subject: [PATCH] Drop home page song collections in favor of a sort feature (#11) --- .../kotlin/it/vfsfitvnm/vimusic/Database.kt | 37 +++- .../vfsfitvnm/vimusic/enums/SongCollection.kt | 7 - .../it/vfsfitvnm/vimusic/enums/SongSortBy.kt | 6 + .../it/vfsfitvnm/vimusic/enums/SortOrder.kt | 11 + .../ui/components/themed/DropdownMenu.kt | 191 ++++++++++++++++++ .../vimusic/ui/screens/HomeScreen.kt | 190 ++++++++++++----- .../it/vfsfitvnm/vimusic/utils/Preferences.kt | 22 +- app/src/main/res/drawable/checkmark.xml | 13 ++ app/src/main/res/drawable/sort.xml | 15 ++ 9 files changed, 418 insertions(+), 74 deletions(-) delete mode 100644 app/src/main/kotlin/it/vfsfitvnm/vimusic/enums/SongCollection.kt create mode 100644 app/src/main/kotlin/it/vfsfitvnm/vimusic/enums/SongSortBy.kt create mode 100644 app/src/main/kotlin/it/vfsfitvnm/vimusic/enums/SortOrder.kt create mode 100644 app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/components/themed/DropdownMenu.kt create mode 100644 app/src/main/res/drawable/checkmark.xml create mode 100644 app/src/main/res/drawable/sort.xml diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/Database.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/Database.kt index a01c119..5bc83e2 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/Database.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/Database.kt @@ -8,9 +8,11 @@ import android.os.Parcel import androidx.media3.common.MediaItem import androidx.room.* import androidx.room.migration.AutoMigrationSpec -import androidx.sqlite.db.SimpleSQLiteQuery import androidx.room.migration.Migration +import androidx.sqlite.db.SimpleSQLiteQuery import androidx.sqlite.db.SupportSQLiteDatabase +import it.vfsfitvnm.vimusic.enums.SongSortBy +import it.vfsfitvnm.vimusic.enums.SortOrder import it.vfsfitvnm.vimusic.models.* import it.vfsfitvnm.vimusic.utils.getFloatOrNull import it.vfsfitvnm.vimusic.utils.getLongOrNull @@ -21,6 +23,35 @@ import kotlinx.coroutines.flow.Flow interface Database { companion object : Database by DatabaseInitializer.Instance.database + @Transaction + @Query("SELECT * FROM Song WHERE totalPlayTimeMs > 0 ORDER BY ROWID ASC") + fun songsByRowIdAsc(): Flow> + + @Transaction + @Query("SELECT * FROM Song WHERE totalPlayTimeMs > 0 ORDER BY ROWID DESC") + fun songsByRowIdDesc(): Flow> + + @Transaction + @Query("SELECT * FROM Song WHERE totalPlayTimeMs > 0 ORDER BY totalPlayTimeMs ASC") + fun songsByPlayTimeAsc(): Flow> + + @Transaction + @Query("SELECT * FROM Song WHERE totalPlayTimeMs > 0 ORDER BY totalPlayTimeMs DESC") + fun songsByPlayTimeDesc(): Flow> + + fun songs(sortBy: SongSortBy, sortOrder: SortOrder): Flow> { + return when (sortBy) { + SongSortBy.PlayTime -> when (sortOrder) { + SortOrder.Ascending -> songsByPlayTimeAsc() + SortOrder.Descending -> songsByPlayTimeDesc() + } + SongSortBy.DateAdded -> when (sortOrder) { + SortOrder.Ascending -> songsByRowIdAsc() + SortOrder.Descending -> songsByRowIdDesc() + } + } + } + @Transaction @Query("SELECT * FROM Song WHERE totalPlayTimeMs > 0 ORDER BY ROWID DESC") fun history(): Flow> @@ -29,10 +60,6 @@ interface Database { @Query("SELECT * FROM Song WHERE likedAt IS NOT NULL ORDER BY likedAt DESC") fun favorites(): Flow> - @Transaction - @Query("SELECT * FROM Song WHERE totalPlayTimeMs >= 60000 ORDER BY totalPlayTimeMs DESC LIMIT 20") - fun mostPlayed(): Flow> - @Query("SELECT * FROM QueuedMediaItem") fun queue(): List diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/enums/SongCollection.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/enums/SongCollection.kt deleted file mode 100644 index 83af2f2..0000000 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/enums/SongCollection.kt +++ /dev/null @@ -1,7 +0,0 @@ -package it.vfsfitvnm.vimusic.enums - -enum class SongCollection { - MostPlayed, - Favorites, - History -} \ No newline at end of file diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/enums/SongSortBy.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/enums/SongSortBy.kt new file mode 100644 index 0000000..2b6be5c --- /dev/null +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/enums/SongSortBy.kt @@ -0,0 +1,6 @@ +package it.vfsfitvnm.vimusic.enums + +enum class SongSortBy { + PlayTime, + DateAdded +} diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/enums/SortOrder.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/enums/SortOrder.kt new file mode 100644 index 0000000..769f02e --- /dev/null +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/enums/SortOrder.kt @@ -0,0 +1,11 @@ +package it.vfsfitvnm.vimusic.enums + +enum class SortOrder { + Ascending, + Descending; + + operator fun not() = when (this) { + Ascending -> Descending + Descending -> Ascending + } +} diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/components/themed/DropdownMenu.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/components/themed/DropdownMenu.kt new file mode 100644 index 0000000..794e095 --- /dev/null +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/components/themed/DropdownMenu.kt @@ -0,0 +1,191 @@ +package it.vfsfitvnm.vimusic.ui.components.themed + +import androidx.compose.animation.core.* +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.TransformOrigin +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.* +import androidx.compose.ui.window.Popup +import androidx.compose.ui.window.PopupPositionProvider +import androidx.compose.ui.window.PopupProperties +import kotlin.math.max +import kotlin.math.min + + +@Composable +fun DropdownMenu( + isDisplayed: Boolean, + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier, + offset: DpOffset = DpOffset(0.dp, 0.dp), + properties: PopupProperties = PopupProperties(focusable = true), + content: @Composable ColumnScope.() -> Unit +) { + val expandedStates = remember { + MutableTransitionState(false) + }.apply { targetState = isDisplayed } + + if (expandedStates.currentState || expandedStates.targetState) { + val density = LocalDensity.current + + var transformOrigin by remember { + mutableStateOf(TransformOrigin.Center) + } + + val popupPositionProvider = + DropdownMenuPositionProvider(offset, density) { parentBounds, menuBounds -> + transformOrigin = calculateTransformOrigin(parentBounds, menuBounds) + } + + Popup( + onDismissRequest = onDismissRequest, + popupPositionProvider = popupPositionProvider, + properties = properties + ) { + DropdownMenuContent( + expandedStates = expandedStates, + transformOrigin = transformOrigin, + modifier = modifier, + content = content + ) + } + } +} + +@Composable +internal fun DropdownMenuContent( + expandedStates: MutableTransitionState, + transformOrigin: TransformOrigin, + modifier: Modifier = Modifier, + content: @Composable ColumnScope.() -> Unit +) { + val transition = updateTransition(expandedStates, "DropDownMenu") + + val scale by transition.animateFloat( + transitionSpec = { + if (false isTransitioningTo true) { + // Dismissed to expanded + tween( + durationMillis = 128, + easing = LinearOutSlowInEasing + ) + } else { + // Expanded to dismissed. + tween( + durationMillis = 64, + delayMillis = 64 + ) + } + }, label = "" + ) { isDisplayed -> + if (isDisplayed) 1f else 0.9f + } + + Column( + modifier = modifier + .graphicsLayer { + scaleX = scale + scaleY = scale + this.transformOrigin = transformOrigin + }, + content = content, + ) +} + +@Immutable +private data class DropdownMenuPositionProvider( + val contentOffset: DpOffset, + val density: Density, + val onPositionCalculated: (IntRect, IntRect) -> Unit = { _, _ -> } +) : PopupPositionProvider { + override fun calculatePosition( + anchorBounds: IntRect, + windowSize: IntSize, + layoutDirection: LayoutDirection, + popupContentSize: IntSize + ): IntOffset { + // The min margin above and below the menu, relative to the screen. + val verticalMargin = with(density) { 48.dp.roundToPx() } + // The content offset specified using the dropdown offset parameter. + val contentOffsetX = with(density) { contentOffset.x.roundToPx() } + val contentOffsetY = with(density) { contentOffset.y.roundToPx() } + + // Compute horizontal position. + val toRight = anchorBounds.left + contentOffsetX + val toLeft = anchorBounds.right - contentOffsetX - popupContentSize.width + val toDisplayRight = windowSize.width - popupContentSize.width + val toDisplayLeft = 0 + val x = if (layoutDirection == LayoutDirection.Ltr) { + sequenceOf( + toRight, + toLeft, + // If the anchor gets outside of the window on the left, we want to position + // toDisplayLeft for proximity to the anchor. Otherwise, toDisplayRight. + if (anchorBounds.left >= 0) toDisplayRight else toDisplayLeft + ) + } else { + sequenceOf( + toLeft, + toRight, + // If the anchor gets outside of the window on the right, we want to position + // toDisplayRight for proximity to the anchor. Otherwise, toDisplayLeft. + if (anchorBounds.right <= windowSize.width) toDisplayLeft else toDisplayRight + ) + }.firstOrNull { + it >= 0 && it + popupContentSize.width <= windowSize.width + } ?: toLeft + + // Compute vertical position. + val toBottom = maxOf(anchorBounds.bottom + contentOffsetY, verticalMargin) + val toTop = anchorBounds.top - contentOffsetY - popupContentSize.height + val toCenter = anchorBounds.top - popupContentSize.height / 2 + val toDisplayBottom = windowSize.height - popupContentSize.height - verticalMargin + val y = sequenceOf(toBottom, toTop, toCenter, toDisplayBottom).firstOrNull { + it >= verticalMargin && + it + popupContentSize.height <= windowSize.height - verticalMargin + } ?: toTop + + onPositionCalculated( + anchorBounds, + IntRect(x, y, x + popupContentSize.width, y + popupContentSize.height) + ) + return IntOffset(x, y) + } +} + +fun calculateTransformOrigin( + parentBounds: IntRect, + menuBounds: IntRect +): TransformOrigin { + val pivotX = when { + menuBounds.left >= parentBounds.right -> 0f + menuBounds.right <= parentBounds.left -> 1f + menuBounds.width == 0 -> 0f + else -> { + val intersectionCenter = + ( + max(parentBounds.left, menuBounds.left) + + min(parentBounds.right, menuBounds.right) + ) / 2 + (intersectionCenter - menuBounds.left).toFloat() / menuBounds.width + } + } + val pivotY = when { + menuBounds.top >= parentBounds.bottom -> 0f + menuBounds.bottom <= parentBounds.top -> 1f + menuBounds.height == 0 -> 0f + else -> { + val intersectionCenter = + ( + max(parentBounds.top, menuBounds.top) + + min(parentBounds.bottom, menuBounds.bottom) + ) / 2 + (intersectionCenter - menuBounds.top).toFloat() / menuBounds.height + } + } + return TransformOrigin(pivotX, pivotY) +} diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/HomeScreen.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/HomeScreen.kt index 69b33c2..73c4175 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/HomeScreen.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/HomeScreen.kt @@ -14,12 +14,14 @@ import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicText import androidx.compose.material.ripple.rememberRipple import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter @@ -27,20 +29,22 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.compose.ui.zIndex import it.vfsfitvnm.route.RouteHandler import it.vfsfitvnm.route.fastFade import it.vfsfitvnm.vimusic.Database import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder import it.vfsfitvnm.vimusic.R -import it.vfsfitvnm.vimusic.enums.SongCollection +import it.vfsfitvnm.vimusic.enums.SongSortBy +import it.vfsfitvnm.vimusic.enums.SortOrder import it.vfsfitvnm.vimusic.enums.ThumbnailRoundness +import it.vfsfitvnm.vimusic.models.DetailedSong import it.vfsfitvnm.vimusic.models.Playlist import it.vfsfitvnm.vimusic.models.SearchQuery -import it.vfsfitvnm.vimusic.models.DetailedSong import it.vfsfitvnm.vimusic.query import it.vfsfitvnm.vimusic.ui.components.TopAppBar -import it.vfsfitvnm.vimusic.ui.components.themed.InFavoritesMediaItemMenu +import it.vfsfitvnm.vimusic.ui.components.themed.DropdownMenu import it.vfsfitvnm.vimusic.ui.components.themed.InHistoryMediaItemMenu import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu import it.vfsfitvnm.vimusic.ui.components.themed.TextFieldDialog @@ -75,12 +79,8 @@ fun HomeScreen() { val preferences = LocalPreferences.current - val songCollection by remember(preferences.homePageSongCollection) { - when (preferences.homePageSongCollection) { - SongCollection.MostPlayed -> Database.mostPlayed() - SongCollection.Favorites -> Database.favorites() - SongCollection.History -> Database.history() - } + val songCollection by remember(preferences.songSortBy, preferences.songSortOrder) { + Database.songs(preferences.songSortBy, preferences.songSortOrder) }.collectAsState(initial = emptyList(), context = Dispatchers.IO) RouteHandler( @@ -313,48 +313,13 @@ fun HomeScreen() { .padding(horizontal = 8.dp) .padding(top = 32.dp) ) { - Row( - verticalAlignment = Alignment.Bottom, + BasicText( + text = "Songs", + style = typography.m.semiBold, modifier = Modifier .weight(1f) .padding(horizontal = 8.dp) - ) { - BasicText( - text = when (preferences.homePageSongCollection) { - SongCollection.MostPlayed -> "Most played" - SongCollection.Favorites -> "Favorites" - SongCollection.History -> "History" - }, - style = typography.m.semiBold, - modifier = Modifier - .alignByBaseline() - .animateContentSize() - ) - - val songCollections = enumValues() - val nextSongCollection = - songCollections[(preferences.homePageSongCollection.ordinal + 1) % songCollections.size] - - BasicText( - text = when (nextSongCollection) { - SongCollection.MostPlayed -> "Most played" - SongCollection.Favorites -> "Favorites" - SongCollection.History -> "History" - }, - style = typography.xxs.secondary.bold, - modifier = Modifier - .clickable( - indication = rememberRipple(bounded = true), - interactionSource = remember { MutableInteractionSource() }, - onClick = { - preferences.homePageSongCollection = nextSongCollection - } - ) - .alignByBaseline() - .padding(horizontal = 16.dp) - .animateContentSize() - ) - } + ) Image( painter = painterResource(R.drawable.shuffle), @@ -372,6 +337,126 @@ fun HomeScreen() { .padding(horizontal = 8.dp, vertical = 8.dp) .size(20.dp) ) + + Box { + var isSortMenuDisplayed by remember { + mutableStateOf(false) + } + + Image( + painter = painterResource(R.drawable.sort), + contentDescription = null, + colorFilter = ColorFilter.tint(colorPalette.text), + modifier = Modifier + .clickable { + isSortMenuDisplayed = true + } + .padding(horizontal = 8.dp, vertical = 8.dp) + .size(20.dp) + ) + + DropdownMenu( + isDisplayed = isSortMenuDisplayed, + onDismissRequest = { + isSortMenuDisplayed = false + } + ) { + @Composable + fun Item( + text: String, + textColor: Color, + backgroundColor: Color, + onClick: () -> Unit + ) { + BasicText( + text = text, + style = typography.xxs.copy(color = textColor, letterSpacing = 1.sp), + modifier = Modifier + .clip(RoundedCornerShape(16.dp)) + .clickable( + indication = rememberRipple(bounded = true), + interactionSource = remember { MutableInteractionSource() }, + onClick = { + isSortMenuDisplayed = false + onClick() + } + ) + .background(backgroundColor) + .fillMaxWidth() + .widthIn(min = 124.dp, max = 248.dp) + .padding(horizontal = 16.dp, vertical = 8.dp) + ) + } + + @Composable + fun Item( + text: String, + isSelected: Boolean, + onClick: () -> Unit + ) { + Item( + text = text, + textColor = if (isSelected) { + colorPalette.onPrimaryContainer + } else { + colorPalette.textSecondary + }, + backgroundColor = if (isSelected) { + colorPalette.primaryContainer + } else { + colorPalette.elevatedBackground + }, + onClick = onClick + ) + } + + Column( + modifier = Modifier + .clip(RoundedCornerShape(16.dp)) + .background(colorPalette.elevatedBackground) + .width(IntrinsicSize.Max), + ) { + Item( + text = "PLAY TIME", + isSelected = preferences.songSortBy == SongSortBy.PlayTime, + onClick = { + preferences.songSortBy = SongSortBy.PlayTime + } + ) + Item( + text = "DATE ADDED", + isSelected = preferences.songSortBy == SongSortBy.DateAdded, + onClick = { + preferences.songSortBy = SongSortBy.DateAdded + } + ) + } + + Spacer( + modifier = Modifier + .height(4.dp) + ) + + Column( + modifier = Modifier + .clip(RoundedCornerShape(16.dp)) + .background(colorPalette.elevatedBackground) + .width(IntrinsicSize.Max), + ) { + Item( + text = when (preferences.songSortOrder) { + SortOrder.Ascending -> "ASCENDING" + SortOrder.Descending -> "DESCENDING" + }, + textColor = colorPalette.text, + backgroundColor = colorPalette.elevatedBackground, + onClick = { + preferences.songSortOrder = !preferences.songSortOrder + } + ) + } + } + } } } @@ -393,15 +478,14 @@ fun HomeScreen() { ) }, menuContent = { - when (preferences.homePageSongCollection) { - SongCollection.MostPlayed -> NonQueuedMediaItemMenu(mediaItem = song.asMediaItem) - SongCollection.Favorites -> InFavoritesMediaItemMenu(song = song) - SongCollection.History -> InHistoryMediaItemMenu(song = song) + when (preferences.songSortBy) { + SongSortBy.PlayTime -> NonQueuedMediaItemMenu(mediaItem = song.asMediaItem) + SongSortBy.DateAdded -> InHistoryMediaItemMenu(song = song) } }, onThumbnailContent = { AnimatedVisibility( - visible = preferences.homePageSongCollection == SongCollection.MostPlayed, + visible = preferences.songSortBy == SongSortBy.PlayTime, enter = fadeIn(), exit = fadeOut(), modifier = Modifier diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/Preferences.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/Preferences.kt index 1718c91..e9a0638 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/Preferences.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/Preferences.kt @@ -6,19 +6,18 @@ import androidx.compose.runtime.* import androidx.compose.ui.platform.LocalContext import androidx.core.content.edit import androidx.media3.common.Player -import it.vfsfitvnm.vimusic.enums.ColorPaletteMode -import it.vfsfitvnm.vimusic.enums.SongCollection -import it.vfsfitvnm.vimusic.enums.ThumbnailRoundness +import it.vfsfitvnm.vimusic.enums.* import it.vfsfitvnm.youtubemusic.YouTube @Stable class Preferences( private val edit: (action: SharedPreferences.Editor.() -> Unit) -> Unit, + initialSongSortBy: SongSortBy, + initialSongSortOrder: SortOrder, initialColorPaletteMode: ColorPaletteMode, initialSearchFilter: String, initialRepeatMode: Int, - initialHomePageSongCollection: SongCollection, initialThumbnailRoundness: ThumbnailRoundness, initialCoilDiskCacheMaxSizeBytes: Long, initialExoPlayerDiskCacheMaxSizeBytes: Long, @@ -30,10 +29,11 @@ class Preferences( edit = { action: SharedPreferences.Editor.() -> Unit -> preferences.edit(action = action) }, + initialSongSortBy = preferences.getEnum(Keys.songSortBy, SongSortBy.DateAdded), + initialSongSortOrder = preferences.getEnum(Keys.songSortOrder, SortOrder.Descending), initialColorPaletteMode = preferences.getEnum(Keys.colorPaletteMode, ColorPaletteMode.System), initialSearchFilter = preferences.getString(Keys.searchFilter, YouTube.Item.Song.Filter.value)!!, initialRepeatMode = preferences.getInt(Keys.repeatMode, Player.REPEAT_MODE_OFF), - initialHomePageSongCollection = preferences.getEnum(Keys.homePageSongCollection, SongCollection.History), initialThumbnailRoundness = preferences.getEnum(Keys.thumbnailRoundness, ThumbnailRoundness.Light), initialCoilDiskCacheMaxSizeBytes = preferences.getLong(Keys.coilDiskCacheMaxSizeBytes, 512L * 1024 * 1024), initialExoPlayerDiskCacheMaxSizeBytes = preferences.getLong(Keys.exoPlayerDiskCacheMaxSizeBytes, 512L * 1024 * 1024), @@ -42,6 +42,12 @@ class Preferences( initialPersistentQueue = preferences.getBoolean(Keys.persistentQueue, false) ) + var songSortBy = initialSongSortBy + set(value) = edit { putEnum(Keys.songSortBy, value) } + + var songSortOrder = initialSongSortOrder + set(value) = edit { putEnum(Keys.songSortOrder, value) } + var colorPaletteMode = initialColorPaletteMode set(value) = edit { putEnum(Keys.colorPaletteMode, value) } @@ -51,9 +57,6 @@ class Preferences( var repeatMode = initialRepeatMode set(value) = edit { putInt(Keys.repeatMode, value) } - var homePageSongCollection = initialHomePageSongCollection - set(value) = edit { putEnum(Keys.homePageSongCollection, value) } - var thumbnailRoundness = initialThumbnailRoundness set(value) = edit { putEnum(Keys.thumbnailRoundness, value) } @@ -73,10 +76,11 @@ class Preferences( set(value) = edit { putBoolean(Keys.persistentQueue, value) } object Keys { + const val songSortOrder = "songSortOrder" + const val songSortBy = "songSortBy" const val colorPaletteMode = "colorPaletteMode" const val searchFilter = "searchFilter" const val repeatMode = "repeatMode" - const val homePageSongCollection = "homePageSongCollection" const val thumbnailRoundness = "thumbnailRoundness" const val coilDiskCacheMaxSizeBytes = "coilDiskCacheMaxSizeBytes" const val exoPlayerDiskCacheMaxSizeBytes = "exoPlayerDiskCacheMaxSizeBytes" diff --git a/app/src/main/res/drawable/checkmark.xml b/app/src/main/res/drawable/checkmark.xml new file mode 100644 index 0000000..1c3a3fe --- /dev/null +++ b/app/src/main/res/drawable/checkmark.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/sort.xml b/app/src/main/res/drawable/sort.xml new file mode 100644 index 0000000..b0ca74f --- /dev/null +++ b/app/src/main/res/drawable/sort.xml @@ -0,0 +1,15 @@ + + + + +