Skip to content

Commit

Permalink
Song sort, closes #68 (#99)
Browse files Browse the repository at this point in the history
  • Loading branch information
Lambada10 authored Oct 29, 2024
1 parent 6e30fbb commit e0d0732
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import pl.lambada.songsync.domain.model.SortOrders
import pl.lambada.songsync.domain.model.SortValues
import pl.lambada.songsync.util.Providers
import pl.lambada.songsync.util.get
import pl.lambada.songsync.util.set
Expand Down Expand Up @@ -50,6 +52,18 @@ class UserSettingsController(private val dataStore: DataStore<Preferences>) {
var showPath by mutableStateOf(dataStore.get(showPathKey, false))
private set

var sortOrder by mutableStateOf(
SortOrders.entries
.find { it.queryName == dataStore.get(sortOrderKey, SortOrders.ASCENDING.queryName) }!!
)
private set

var sortBy by mutableStateOf(
SortValues.entries
.find { it.name == dataStore.get(sortByKey, SortValues.TITLE.name) }!!
)
private set

fun updateEmbedLyrics(to: Boolean) {
dataStore.set(embedKey, to)
embedLyricsIntoFiles = to
Expand Down Expand Up @@ -104,6 +118,16 @@ class UserSettingsController(private val dataStore: DataStore<Preferences>) {
dataStore.set(showPathKey, to)
showPath = to
}

fun updateSortOrder(to: SortOrders) {
dataStore.set(sortOrderKey, to.queryName)
sortOrder = to
}

fun updateSortBy(to: SortValues) {
dataStore.set(sortByKey, to.name)
sortBy = to
}
}

private val embedKey = booleanPreferencesKey("embed_lyrics")
Expand All @@ -116,4 +140,6 @@ private val syncedMusixmatchKey = booleanPreferencesKey("synced_lyrics")
private val disableMarqueeKey = booleanPreferencesKey("marquee_disable")
private val pureBlackKey = booleanPreferencesKey("pure_black")
private val sdCardPathKey = stringPreferencesKey("sd_card_path")
private val showPathKey = booleanPreferencesKey("show_path")
private val showPathKey = booleanPreferencesKey("show_path")
private val sortOrderKey = stringPreferencesKey("sort_order")
private val sortByKey = stringPreferencesKey("sort_by")
17 changes: 17 additions & 0 deletions app/src/main/java/pl/lambada/songsync/domain/model/Sort.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package pl.lambada.songsync.domain.model

import androidx.annotation.StringRes
import pl.lambada.songsync.R

enum class SortOrders(val queryName: String, @StringRes val displayName: Int) {
ASCENDING("ASC", R.string.ascending),
DESCENDING("DESC", R.string.descending),
}

enum class SortValues(@StringRes val displayName: Int) {
TITLE(R.string.title),
ARTIST(R.string.artist),
ALBUM(R.string.album),
YEAR(R.string.year),
DURATION(R.string.duration),
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
Expand All @@ -36,6 +39,8 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import pl.lambada.songsync.domain.model.SortOrders
import pl.lambada.songsync.domain.model.SortValues
import pl.lambada.songsync.ui.LyricsFetchScreen
import pl.lambada.songsync.ui.ScreenAbout
import pl.lambada.songsync.ui.screens.home.components.BatchDownloadLyrics
Expand All @@ -45,6 +50,7 @@ import pl.lambada.songsync.ui.screens.home.components.HomeAppBar
import pl.lambada.songsync.ui.screens.home.components.HomeSearchBar
import pl.lambada.songsync.ui.screens.home.components.HomeSearchThing
import pl.lambada.songsync.ui.screens.home.components.SongItem
import pl.lambada.songsync.ui.screens.home.components.SortDialog
import pl.lambada.songsync.util.ext.BackPressHandler
import pl.lambada.songsync.util.ext.lowercaseWithLocale

Expand All @@ -65,7 +71,10 @@ fun HomeScreen(
var isBatchDownload by remember { mutableStateOf(false) }
val context = LocalContext.current

SideEffect { viewModel.updateAllSongs(context) }
LaunchedEffect(viewModel.userSettingsController.sortBy to viewModel.userSettingsController.sortOrder) {
viewModel.cachedSongs = null
viewModel.updateAllSongs(context, viewModel.userSettingsController.sortBy, viewModel.userSettingsController.sortOrder)
}

Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
Expand Down Expand Up @@ -209,6 +218,7 @@ fun HomeScreenLoaded(
FilterAndSongCount(
displaySongsCount = viewModel.displaySongs.size,
onFilterClick = { viewModel.showFilters = true },
onSortClick = { viewModel.showSort = true },
onSearchClick = {
viewModel.showSearch = true
viewModel.showingSearch = true
Expand All @@ -217,6 +227,15 @@ fun HomeScreenLoaded(
}
)

if (viewModel.showSort) {
SortDialog(
userSettingsController = viewModel.userSettingsController,
onDismiss = { viewModel.showSort = false },
onSortOrderChange = { viewModel.userSettingsController.updateSortOrder(it) },
onSortByChange = { viewModel.userSettingsController.updateSortBy(it) }
)
}

if (viewModel.showFilters) {
FiltersDialog(
hideLyrics = viewModel.userSettingsController.hideLyrics,
Expand All @@ -231,6 +250,7 @@ fun HomeScreenLoaded(
}
}


items(viewModel.displaySongs.size) { index ->
val song = viewModel.displaySongs[index]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import pl.lambada.songsync.data.remote.UserSettingsController
import pl.lambada.songsync.data.remote.lyrics_providers.LyricsProviderService
import pl.lambada.songsync.domain.model.Song
import pl.lambada.songsync.domain.model.SongInfo
import pl.lambada.songsync.domain.model.SortOrders
import pl.lambada.songsync.domain.model.SortValues
import pl.lambada.songsync.util.downloadLyrics
import pl.lambada.songsync.util.ext.toLrcFile

Expand All @@ -35,7 +37,7 @@ class HomeViewModel(
val userSettingsController: UserSettingsController,
private val lyricsProviderService: LyricsProviderService
) : ViewModel() {
private var cachedSongs: List<Song>? = null
var cachedSongs: List<Song>? = null
val selectedSongs = mutableStateListOf<String>()
var allSongs by mutableStateOf<List<Song>?>(null)

Expand All @@ -60,6 +62,7 @@ class HomeViewModel(
)

var showFilters by mutableStateOf(false)
var showSort by mutableStateOf(false)
var showingSearch by mutableStateOf(false)
var showSearch by mutableStateOf(showingSearch)

Expand Down Expand Up @@ -90,16 +93,16 @@ class HomeViewModel(
}
}

fun updateAllSongs(context: Context) = viewModelScope.launch(Dispatchers.IO) {
allSongs = getAllSongs(context)
fun updateAllSongs(context: Context, sortBy: SortValues, sortOrder: SortOrders) = viewModelScope.launch(Dispatchers.IO) {
allSongs = getAllSongs(context, sortBy, sortOrder)
}

/**
* Loads all songs from the MediaStore.
* @param context The application context.
* @return A list of Song objects representing the songs.
*/
private fun getAllSongs(context: Context): List<Song> {
private fun getAllSongs(context: Context, sortBy: SortValues, sortOrder: SortOrders): List<Song> {
return cachedSongs ?: run {
val selection = MediaStore.Audio.Media.IS_MUSIC + "!= 0"
val projection = arrayOf(
Expand All @@ -109,7 +112,7 @@ class HomeViewModel(
MediaStore.Audio.Media.DATA,
MediaStore.Audio.Media.ALBUM_ID,
)
val sortOrder = MediaStore.Audio.Media.TITLE + " ASC"
val sortOrder = sortBy.name + " " + sortOrder.queryName

val songs = mutableListOf<Song>()
val cursor = context.contentResolver.query(
Expand Down Expand Up @@ -189,7 +192,7 @@ class HomeViewModel(
return cachedFolders ?: run {
val folders = mutableListOf<String>()

for (song in getAllSongs(context)) {
for (song in getAllSongs(context, SortValues.TITLE, SortOrders.ASCENDING)) {
val path = song.filePath
val folder = path?.substring(0, path.lastIndexOf("/"))
if (folder != null && !folders.contains(folder))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Sort
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Sort
import androidx.compose.material.icons.outlined.FilterAlt
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
Expand All @@ -21,6 +23,7 @@ import pl.lambada.songsync.R
fun FilterAndSongCount(
displaySongsCount: Int,
onFilterClick: () -> Unit,
onSortClick: () -> Unit,
onSearchClick: () -> Unit
) {
Row(
Expand All @@ -29,6 +32,13 @@ fun FilterAndSongCount(
) {
Text(text = "$displaySongsCount songs")
Spacer(modifier = Modifier.weight(1f))
IconButton(onClick = onSortClick) {
Icon(
Icons.AutoMirrored.Filled.Sort,
contentDescription = stringResource(R.string.sort),
)
}

IconButton(onClick = onFilterClick) {
Icon(
Icons.Outlined.FilterAlt,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package pl.lambada.songsync.ui.screens.home.components

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import pl.lambada.songsync.R
import pl.lambada.songsync.data.remote.UserSettingsController
import pl.lambada.songsync.domain.model.SortOrders
import pl.lambada.songsync.domain.model.SortValues

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SortDialog(
userSettingsController: UserSettingsController,
onDismiss: () -> Unit,
onSortByChange: (SortValues) -> Unit,
onSortOrderChange: (SortOrders) -> Unit
) {
BasicAlertDialog(onDismiss) {
Surface(
modifier = Modifier
.wrapContentWidth()
.wrapContentHeight(),
shape = MaterialTheme.shapes.large,
tonalElevation = AlertDialogDefaults.TonalElevation
) {
Column {
Text(
text = stringResource(R.string.sort),
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(top = 24.dp, start = 24.dp, bottom = 16.dp)
)
SortByRadioGroup(userSettingsController, onSortByChange)
HorizontalDivider()
SortOrderRadioGroup(userSettingsController, onSortOrderChange)
Row(modifier = Modifier.padding(16.dp)) {
Spacer(modifier = Modifier.weight(1f))
TextButton(onDismiss) {
Text(stringResource(R.string.close))
}
}
}
}
}
}

@Composable
fun SortOrderRadioGroup(userSettingsController: UserSettingsController, onSortOrderChange: (SortOrders) -> Unit) {
Column {
SortOrders.entries.forEach {
Row(verticalAlignment = Alignment.CenterVertically) {
RadioButton(
selected = userSettingsController.sortOrder == it,
onClick = {
onSortOrderChange(it)
}
)
Text(
stringResource(it.displayName),
modifier = Modifier.clickable {
onSortOrderChange(it)
}
)
}
}
}
}

@Composable
fun SortByRadioGroup(userSettingsController: UserSettingsController, onSortByChange: (SortValues) -> Unit) {
Column {
SortValues.entries.forEach {
Row(verticalAlignment = Alignment.CenterVertically) {
RadioButton(
selected = userSettingsController.sortBy == it,
onClick = {
onSortByChange(it)
}
)
Text(
stringResource(it.displayName),
modifier = Modifier.clickable {
onSortByChange(it)
}
)
}
}
}
}
8 changes: 8 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,12 @@
<string name="song_path_description">Show the path of a song file on song list</string>
<string name="synced_lyrics">Get Synced Lyrics</string>
<string name="synced_lyrics_summary">Toggle to switch between synced and unsynced lyrics from Musixmatch.</string>
<string name="title">Title</string>
<string name="artist">Artist</string>
<string name="album">Album</string>
<string name="year">Year</string>
<string name="duration">Duration</string>
<string name="ascending">Ascending</string>
<string name="descending">Descending</string>
<string name="sort">Sort</string>
</resources>

0 comments on commit e0d0732

Please sign in to comment.