diff --git a/app/.DS_Store b/app/.DS_Store
index 693cf22..b435c59 100644
Binary files a/app/.DS_Store and b/app/.DS_Store differ
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index ce6b446..fc7ad66 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -15,8 +15,8 @@ android {
applicationId = "com.sosauce.cutemusic"
minSdk = 26
targetSdk = 35
- versionCode = 20
- versionName = "2.3.3"
+ versionCode = 21
+ versionName = "2.3.4"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
@@ -54,14 +54,14 @@ android {
}
-// splits {
-// abi {
-// isEnable = true
-// reset()
-// include("armeabi-v7a", "arm64-v8a")
-// isUniversalApk = false
-// }
-// }
+ splits {
+ abi {
+ isEnable = true
+ reset()
+ include("armeabi-v7a", "arm64-v8a")
+ isUniversalApk = true
+ }
+ }
}
dependencies {
diff --git a/app/release/baselineProfiles/0/app-release.dm b/app/release/baselineProfiles/0/app-release.dm
index 0742d85..a47f464 100644
Binary files a/app/release/baselineProfiles/0/app-release.dm and b/app/release/baselineProfiles/0/app-release.dm differ
diff --git a/app/release/baselineProfiles/1/app-release.dm b/app/release/baselineProfiles/1/app-release.dm
index 05d5a95..c46f920 100644
Binary files a/app/release/baselineProfiles/1/app-release.dm and b/app/release/baselineProfiles/1/app-release.dm differ
diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json
index cb11106..a2adf5f 100644
--- a/app/release/output-metadata.json
+++ b/app/release/output-metadata.json
@@ -11,8 +11,8 @@
"type": "SINGLE",
"filters": [],
"attributes": [],
- "versionCode": 19,
- "versionName": "2.3.2",
+ "versionCode": 20,
+ "versionName": "2.3.3",
"outputFile": "app-release.apk"
}
],
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ea3033c..5248757 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -19,6 +19,7 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/sosauce/cutemusic/data/datastore/DataStore.kt b/app/src/main/java/com/sosauce/cutemusic/data/datastore/DataStore.kt
index 7c6de4a..41b6306 100644
--- a/app/src/main/java/com/sosauce/cutemusic/data/datastore/DataStore.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/data/datastore/DataStore.kt
@@ -1,8 +1,10 @@
package com.sosauce.cutemusic.data.datastore
import android.content.Context
+import android.util.Log
import androidx.compose.runtime.Composable
import androidx.datastore.core.DataStore
+import androidx.datastore.core.IOException
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.stringSetPreferencesKey
@@ -11,6 +13,7 @@ import com.sosauce.cutemusic.data.datastore.PreferencesKeys.APPLY_LOOP
import com.sosauce.cutemusic.data.datastore.PreferencesKeys.BLACKLISTED_FOLDERS
import com.sosauce.cutemusic.data.datastore.PreferencesKeys.FOLLOW_SYS
import com.sosauce.cutemusic.data.datastore.PreferencesKeys.HAS_SEEN_TIP
+import com.sosauce.cutemusic.data.datastore.PreferencesKeys.SAF_TRACKS
import com.sosauce.cutemusic.data.datastore.PreferencesKeys.SHOW_ALBUMS_TAB
import com.sosauce.cutemusic.data.datastore.PreferencesKeys.SHOW_ARTISTS_TAB
import com.sosauce.cutemusic.data.datastore.PreferencesKeys.SHOW_FOLDERS_TAB
@@ -21,7 +24,10 @@ import com.sosauce.cutemusic.data.datastore.PreferencesKeys.USE_ART_THEME
import com.sosauce.cutemusic.data.datastore.PreferencesKeys.USE_CLASSIC_SLIDER
import com.sosauce.cutemusic.data.datastore.PreferencesKeys.USE_DARK_MODE
import com.sosauce.cutemusic.data.datastore.PreferencesKeys.USE_SYSTEM_FONT
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
private const val PREFERENCES_NAME = "settings"
@@ -43,6 +49,7 @@ private data object PreferencesKeys {
val SHOW_ALBUMS_TAB = booleanPreferencesKey("show_albums_tab")
val SHOW_ARTISTS_TAB = booleanPreferencesKey("show_artists_tab")
val SHOW_FOLDERS_TAB = booleanPreferencesKey("show_folders_tab")
+ val SAF_TRACKS = stringSetPreferencesKey("saf_tracks")
}
@Composable
@@ -103,9 +110,25 @@ fun rememberShowArtistsTab() =
fun rememberShowFoldersTab() =
rememberPreference(key = SHOW_FOLDERS_TAB, defaultValue = true)
+@Composable
+fun rememberAllSafTracks() =
+ rememberPreference(key = SAF_TRACKS, defaultValue = emptySet())
+
suspend fun getBlacklistedFolder(context: Context): Set {
val preferences = context.dataStore.data.first()
return preferences[BLACKLISTED_FOLDERS] ?: emptySet()
}
+fun getSafTracks(context: Context): Flow> =
+
+ context.dataStore.data
+ .catch { exception ->
+ if (exception is IOException) {
+ Log.d("CuteError", "getSafTracks: ${exception.message}")
+ } else throw exception
+ }
+ .map { preference ->
+ preference[SAF_TRACKS] ?: emptySet()
+ }
+
diff --git a/app/src/main/java/com/sosauce/cutemusic/di/AppModule.kt b/app/src/main/java/com/sosauce/cutemusic/di/AppModule.kt
index 4656c8d..f398c81 100644
--- a/app/src/main/java/com/sosauce/cutemusic/di/AppModule.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/di/AppModule.kt
@@ -2,6 +2,7 @@ package com.sosauce.cutemusic.di
import com.sosauce.cutemusic.domain.repository.MediaStoreHelper
import com.sosauce.cutemusic.domain.repository.MediaStoreHelperImpl
+import com.sosauce.cutemusic.domain.repository.SafManager
import com.sosauce.cutemusic.ui.screens.metadata.MetadataViewModel
import com.sosauce.cutemusic.ui.shared_components.MusicViewModel
import com.sosauce.cutemusic.ui.shared_components.PostViewModel
@@ -14,11 +15,16 @@ val appModule = module {
single {
MediaStoreHelperImpl(androidContext())
}
+
+ single {
+ SafManager(androidContext())
+ }
+
viewModel {
- PostViewModel(get())
+ PostViewModel(get(), get())
}
viewModel {
- MusicViewModel(androidApplication(), get())
+ MusicViewModel(androidApplication(), get(), get())
}
viewModel {
MetadataViewModel(androidApplication())
diff --git a/app/src/main/java/com/sosauce/cutemusic/domain/repository/MediaStoreHelper.kt b/app/src/main/java/com/sosauce/cutemusic/domain/repository/MediaStoreHelper.kt
index dbf2943..b5ad9d5 100644
--- a/app/src/main/java/com/sosauce/cutemusic/domain/repository/MediaStoreHelper.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/domain/repository/MediaStoreHelper.kt
@@ -7,7 +7,7 @@ import androidx.media3.common.MediaItem
import com.sosauce.cutemusic.domain.model.Album
import com.sosauce.cutemusic.domain.model.Artist
import com.sosauce.cutemusic.domain.model.Folder
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
interface MediaStoreHelper {
@@ -17,10 +17,10 @@ interface MediaStoreHelper {
val folders: List
fun fetchMusics(): List
- fun fetchLatestMusics(): Flow>
+ fun fetchLatestMusics(): StateFlow>
fun fetchAlbums(): List
- fun fetchLatestAlbums(): Flow>
+ fun fetchLatestAlbums(): StateFlow>
fun fetchArtists(): List
diff --git a/app/src/main/java/com/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl.kt b/app/src/main/java/com/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl.kt
index eae9bfb..d634638 100644
--- a/app/src/main/java/com/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl.kt
@@ -21,9 +21,13 @@ import com.sosauce.cutemusic.domain.model.Album
import com.sosauce.cutemusic.domain.model.Artist
import com.sosauce.cutemusic.domain.model.Folder
import com.sosauce.cutemusic.utils.observe
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.runBlocking
@SuppressLint("UnsafeOptInUsageError")
@@ -31,26 +35,41 @@ class MediaStoreHelperImpl(
private val context: Context
) : MediaStoreHelper {
+
private fun getBlacklistedFoldersAsync(): Set =
runBlocking { getBlacklistedFolder(context) }
+
private val blacklistedFolders = getBlacklistedFoldersAsync()
private val selection =
blacklistedFolders.joinToString(" AND ") { "${MediaStore.Audio.Media.DATA} NOT LIKE ?" }
private val selectionArgs = blacklistedFolders.map { "$it%" }.toTypedArray()
- override fun fetchLatestMusics(): Flow> =
+ override fun fetchLatestMusics(): StateFlow> =
context.contentResolver.observe(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI)
- .map { fetchMusics() }
+ .map {
+ fetchMusics()
+ }
+ .stateIn(
+ CoroutineScope(Dispatchers.IO),
+ SharingStarted.WhileSubscribed(5000),
+ listOf()
+ )
- override fun fetchLatestAlbums(): Flow> =
+ override fun fetchLatestAlbums(): StateFlow> =
context.contentResolver.observe(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI)
.map { fetchAlbums() }
+ .stateIn(
+ CoroutineScope(Dispatchers.IO),
+ SharingStarted.WhileSubscribed(5000),
+ listOf()
+ )
@UnstableApi
override fun fetchMusics(): List {
+
val musics = mutableListOf()
val projection = arrayOf(
@@ -62,7 +81,8 @@ class MediaStoreHelperImpl(
MediaStore.Audio.Media.ALBUM_ID,
MediaStore.Audio.Media.DATA,
MediaStore.Audio.Media.SIZE,
- MediaStore.Audio.Media.DURATION
+ MediaStore.Audio.Media.DURATION,
+ MediaStore.Audio.Media.TRACK,
)
@@ -82,6 +102,7 @@ class MediaStoreHelperImpl(
val folderColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA)
val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.SIZE)
val durationColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION)
+ val trackNbColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TRACK)
//val isFavColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.IS_FAVORITE)
while (cursor.moveToNext()) {
@@ -95,6 +116,7 @@ class MediaStoreHelperImpl(
val folder = filePath.substring(0, filePath.lastIndexOf('/'))
val size = cursor.getLong(sizeColumn)
val duration = cursor.getLong(durationColumn)
+ val trackNumber = cursor.getInt(trackNbColumn)
//val isFavorite = cursor.getInt(isFavColumn) // 1 = is favorite, 0 = no
val uri = ContentUris.withAppendedId(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
@@ -119,6 +141,7 @@ class MediaStoreHelperImpl(
.setAlbumTitle(album)
.setArtworkUri(artUri)
.setDurationMs(duration)
+ .setTrackNumber(trackNumber)
.setExtras(
Bundle()
.apply {
@@ -128,6 +151,7 @@ class MediaStoreHelperImpl(
putString("uri", uri.toString())
putLong("album_id", albumId)
putLong("artist_id", artistId)
+ putBoolean("is_saf", false)
// putInt("isFavorite", isFavorite)
}).build()
)
diff --git a/app/src/main/java/com/sosauce/cutemusic/domain/repository/SafManager.kt b/app/src/main/java/com/sosauce/cutemusic/domain/repository/SafManager.kt
new file mode 100644
index 0000000..2de943f
--- /dev/null
+++ b/app/src/main/java/com/sosauce/cutemusic/domain/repository/SafManager.kt
@@ -0,0 +1,124 @@
+package com.sosauce.cutemusic.domain.repository
+
+import android.content.Context
+import android.media.MediaMetadataRetriever
+import android.net.Uri
+import android.os.Bundle
+import android.util.Log
+import androidx.core.net.toUri
+import androidx.media3.common.MediaItem
+import androidx.media3.common.MediaMetadata
+import androidx.media3.common.util.UnstableApi
+import com.sosauce.cutemusic.data.datastore.getSafTracks
+import com.sosauce.cutemusic.utils.getUriFromByteArray
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+import java.util.UUID
+
+class SafManager(
+ private val context: Context
+) {
+
+
+ @UnstableApi
+ fun fetchLatestSafTracks(): StateFlow> = getSafTracks(context)
+ .map { tracks ->
+ tracks.map { uri ->
+ uriToMediaItem(uri.toUri())
+ }
+ }
+ .stateIn(
+ CoroutineScope(Dispatchers.IO),
+ SharingStarted.WhileSubscribed(5000),
+ listOf()
+ )
+
+
+ @UnstableApi
+ private suspend fun uriToMediaItem(uri: Uri): MediaItem = withContext(Dispatchers.IO) {
+
+ val retriever = MediaMetadataRetriever()
+
+ try {
+ retriever.setDataSource(context, uri)
+
+ val id = uri.hashCode()
+ val title = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE)
+ val artist = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST)
+ val album = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM)
+ val size =
+ context.contentResolver.openAssetFileDescriptor(uri, "r")?.use { it.length } ?: 0
+ val duration = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
+ val artUri = retriever.embeddedPicture?.getUriFromByteArray(context)
+
+ return@withContext MediaItem
+ .Builder()
+ .setUri(uri)
+ .setMediaId(id.toString())
+ .setMediaMetadata(
+ MediaMetadata
+ .Builder()
+ .setIsBrowsable(false)
+ .setIsPlayable(true)
+ .setTitle(title)
+ .setArtist(artist)
+ .setAlbumTitle(album)
+ .setArtworkUri(artUri)
+ .setDurationMs(duration?.toLong() ?: 0)
+ .setExtras(
+ Bundle()
+ .apply {
+ putString("folder", "SAF")
+ putLong("size", size)
+ putString("path", "${uri.path}")
+ putString("uri", uri.toString())
+ putLong("album_id", 0)
+ putLong("artist_id", 0)
+ putBoolean("is_saf", true)
+ // putInt("isFavorite", isFavorite)
+ }).build()
+ )
+ .build()
+
+ } catch (e: Exception) {
+ Log.d("FAILED_SAF", "uriToMediaItem: ${e.stackTrace} ${e.message}")
+ } finally {
+ retriever.release()
+ }
+
+ return@withContext MediaItem
+ .Builder()
+ .setUri(uri)
+ .setMediaId(UUID.randomUUID().toString())
+ .setMediaMetadata(
+ MediaMetadata
+ .Builder()
+ .setIsBrowsable(false)
+ .setIsPlayable(true)
+ .setTitle("No title")
+ .setArtist("No artist")
+ .setAlbumTitle("No album")
+ .setArtworkUri(Uri.EMPTY)
+ .setDurationMs(0)
+ .setExtras(
+ Bundle()
+ .apply {
+ putString("folder", "SAF")
+ putLong("size", 0)
+ putString("path", "${uri.path}")
+ putString("uri", uri.toString())
+ putLong("album_id", 0)
+ putLong("artist_id", 0)
+ putBoolean("is_saf", true)
+ // putInt("isFavorite", isFavorite)
+ }).build()
+ )
+ .build()
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sosauce/cutemusic/main/AutoPlaybackService.kt b/app/src/main/java/com/sosauce/cutemusic/main/AutoPlaybackService.kt
new file mode 100644
index 0000000..1ce2d97
--- /dev/null
+++ b/app/src/main/java/com/sosauce/cutemusic/main/AutoPlaybackService.kt
@@ -0,0 +1,70 @@
+package com.sosauce.cutemusic.main
+
+import android.content.Intent
+import android.media.MediaDescription
+import android.media.browse.MediaBrowser
+import android.net.Uri
+import android.os.Bundle
+import android.service.media.MediaBrowserService
+import androidx.media3.common.util.UnstableApi
+import com.sosauce.cutemusic.domain.repository.MediaStoreHelperImpl
+import com.sosauce.cutemusic.utils.ROOT_ID
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+@UnstableApi
+class AutoPlaybackService : MediaBrowserService() {
+
+
+ val mediaStoreHelper by lazy { MediaStoreHelperImpl(this) }
+ private val job = SupervisorJob()
+ private val scope = CoroutineScope(Dispatchers.IO + job)
+
+
+ override fun onGetRoot(
+ clientPackageName: String,
+ clientUid: Int,
+ rootHints: Bundle?
+ ): BrowserRoot? = BrowserRoot(ROOT_ID, null)
+
+ override fun onLoadChildren(
+ parentId: String,
+ result: Result?>
+ ) {
+
+ val mediaItems: MutableList = mutableListOf()
+
+ if (ROOT_ID == parentId) {
+ scope.launch {
+ mediaStoreHelper.fetchLatestMusics().collectLatest { list ->
+ list.forEach { mediaItem ->
+ mediaItems.add(
+ MediaBrowser.MediaItem(
+ MediaDescription.Builder()
+ .setMediaId(mediaItem.mediaId)
+ .setTitle(mediaItem.mediaMetadata.title ?: "No title")
+ .setIconUri(mediaItem.mediaMetadata.artworkUri ?: Uri.EMPTY)
+ .build(),
+ MediaBrowser.MediaItem.FLAG_PLAYABLE
+ )
+ )
+ }
+ }
+ result.sendResult(mediaItems)
+ }
+ } else result.sendResult(listOf())
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ job.cancel()
+ }
+
+ override fun onTaskRemoved(rootIntent: Intent?) {
+ super.onTaskRemoved(rootIntent)
+ job.cancel()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sosauce/cutemusic/main/PlaybackService.kt b/app/src/main/java/com/sosauce/cutemusic/main/PlaybackService.kt
index 574dd61..1c68172 100644
--- a/app/src/main/java/com/sosauce/cutemusic/main/PlaybackService.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/main/PlaybackService.kt
@@ -13,6 +13,7 @@ import androidx.media3.session.MediaLibraryService
import androidx.media3.session.MediaLibraryService.MediaLibrarySession
import androidx.media3.session.MediaSession
import com.sosauce.cutemusic.R
+import com.sosauce.cutemusic.utils.CUTE_MUSIC_ID
class PlaybackService : MediaLibraryService(),
@@ -45,6 +46,7 @@ class PlaybackService : MediaLibraryService(),
.build()
mediaLibrarySession = MediaLibrarySession
.Builder(this, player, this)
+ .setId(CUTE_MUSIC_ID)
.setShowPlayButtonIfPlaybackIsSuppressed(false)
// .setBitmapLoader(object : BitmapLoader {
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/navigation/Navigation.kt b/app/src/main/java/com/sosauce/cutemusic/ui/navigation/Navigation.kt
index 2122a1d..7a74dc8 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/navigation/Navigation.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/navigation/Navigation.kt
@@ -23,6 +23,7 @@ import com.sosauce.cutemusic.ui.screens.main.MainScreen
import com.sosauce.cutemusic.ui.screens.metadata.MetadataEditor
import com.sosauce.cutemusic.ui.screens.metadata.MetadataViewModel
import com.sosauce.cutemusic.ui.screens.playing.NowPlayingScreen
+import com.sosauce.cutemusic.ui.screens.saf.SafScreen
import com.sosauce.cutemusic.ui.screens.settings.SettingsScreen
import com.sosauce.cutemusic.ui.shared_components.MusicViewModel
import com.sosauce.cutemusic.ui.shared_components.PostViewModel
@@ -222,6 +223,19 @@ fun Nav() {
animatedVisibilityScope = this,
)
}
+
+ composable {
+ val latestSafTracks by postViewModel.safTracks.collectAsStateWithLifecycle()
+
+ SafScreen(
+ onNavigateUp = navController::navigateUp,
+ latestSafTracks = latestSafTracks,
+ onNavigate = { navController.navigate(it) },
+ onShortClick = { viewModel.handlePlayerActions(PlayerActions.StartPlayback(it)) },
+ isPlayerReady = musicState.isPlayerReady,
+ currentMusicUri = musicState.currentMusicUri,
+ )
+ }
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/navigation/Screen.kt b/app/src/main/java/com/sosauce/cutemusic/ui/navigation/Screen.kt
index a4c083d..597466e 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/navigation/Screen.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/navigation/Screen.kt
@@ -25,6 +25,9 @@ sealed class Screen {
@Serializable
data object AllFolders : Screen()
+ @Serializable
+ data object Saf : Screen()
+
@Serializable
data class AlbumsDetails(
val id: Long
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/screens/album/AlbumDetailsScreen.kt b/app/src/main/java/com/sosauce/cutemusic/ui/screens/album/AlbumDetailsScreen.kt
index b999915..604e2e7 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/screens/album/AlbumDetailsScreen.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/screens/album/AlbumDetailsScreen.kt
@@ -144,7 +144,11 @@ private fun SharedTransitionScope.AlbumDetailsContent(
contentDescription = stringResource(R.string.artwork),
modifier = Modifier
.size(150.dp)
- .clip(RoundedCornerShape(12.dp)),
+ .sharedElement(
+ state = rememberSharedContentState(key = album.id),
+ animatedVisibilityScope = animatedVisibilityScope,
+ )
+ .clip(RoundedCornerShape(24.dp)),
contentScale = ContentScale.Crop
)
Spacer(Modifier.width(10.dp))
@@ -154,13 +158,23 @@ private fun SharedTransitionScope.AlbumDetailsContent(
CuteText(
text = album.name,
fontSize = 22.sp,
- modifier = Modifier.basicMarquee()
+ modifier = Modifier
+ .sharedElement(
+ state = rememberSharedContentState(key = album.name + album.id),
+ animatedVisibilityScope = animatedVisibilityScope,
+ )
+ .basicMarquee()
)
CuteText(
text = album.artist,
fontSize = 22.sp,
color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.85f),
- modifier = Modifier.basicMarquee()
+ modifier = Modifier
+ .sharedElement(
+ state = rememberSharedContentState(key = album.artist + album.id),
+ animatedVisibilityScope = animatedVisibilityScope,
+ )
+ .basicMarquee()
)
Spacer(Modifier.height(60.dp))
CuteText(
@@ -172,21 +186,26 @@ private fun SharedTransitionScope.AlbumDetailsContent(
}
Spacer(Modifier.height(10.dp))
Column {
- albumSongs.forEach { music ->
- MusicListItem(
- music = music,
- onShortClick = {
- viewModel.handlePlayerActions(
- PlayerActions.StartAlbumPlayback(
- albumName = music.mediaMetadata.albumTitle.toString(),
- mediaId = it
+ albumSongs.sortedWith(compareBy(
+ { it.mediaMetadata.trackNumber == null || it.mediaMetadata.trackNumber == 0 },
+ { it.mediaMetadata.trackNumber }
+ ))
+ .forEach { music ->
+ MusicListItem(
+ music = music,
+ onShortClick = {
+ viewModel.handlePlayerActions(
+ PlayerActions.StartAlbumPlayback(
+ albumName = music.mediaMetadata.albumTitle.toString(),
+ mediaId = it
+ )
)
- )
- },
- currentMusicUri = musicState.currentMusicUri,
- isPlayerReady = musicState.isPlayerReady
- )
- }
+ },
+ currentMusicUri = musicState.currentMusicUri,
+ isPlayerReady = musicState.isPlayerReady,
+ showTrackNumber = true
+ )
+ }
}
}
}
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/screens/album/AlbumScreen.kt b/app/src/main/java/com/sosauce/cutemusic/ui/screens/album/AlbumScreen.kt
index c3a1dd2..e3b8dcb 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/screens/album/AlbumScreen.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/screens/album/AlbumScreen.kt
@@ -142,9 +142,11 @@ fun SharedTransitionScope.AlbumsScreen(
.thenIf(
if (isLandscape)
index == 0 || index == 1 || index == 2 || index == 3
- else index == 0 || index == 1,
+ else index == 0 || index == 1
+ ) {
Modifier.statusBarsPadding()
- )
+ },
+ animatedVisibilityScope = animatedVisibilityScope
)
}
}
@@ -223,9 +225,10 @@ fun SharedTransitionScope.AlbumsScreen(
@Composable
-fun AlbumCard(
+fun SharedTransitionScope.AlbumCard(
album: Album,
- modifier: Modifier = Modifier
+ modifier: Modifier = Modifier,
+ animatedVisibilityScope: AnimatedVisibilityScope,
) {
val context = LocalContext.current
@@ -241,6 +244,10 @@ fun AlbumCard(
contentDescription = stringResource(id = R.string.artwork),
modifier = Modifier
.size(160.dp)
+ .sharedElement(
+ state = rememberSharedContentState(key = album.id),
+ animatedVisibilityScope = animatedVisibilityScope,
+ )
.clip(RoundedCornerShape(24.dp)),
contentScale = ContentScale.Crop
)
@@ -249,12 +256,22 @@ fun AlbumCard(
CuteText(
text = album.name,
maxLines = 1,
- modifier = Modifier.basicMarquee()
+ modifier = Modifier
+ .sharedElement(
+ state = rememberSharedContentState(key = album.name + album.id),
+ animatedVisibilityScope = animatedVisibilityScope,
+ )
+ .basicMarquee()
)
CuteText(
text = album.artist,
color = MaterialTheme.colorScheme.onBackground.copy(0.85f),
- modifier = Modifier.basicMarquee()
+ modifier = Modifier
+ .sharedElement(
+ state = rememberSharedContentState(key = album.artist + album.id),
+ animatedVisibilityScope = animatedVisibilityScope,
+ )
+ .basicMarquee()
)
}
}
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/screens/all_folders/AllFoldersScreen.kt b/app/src/main/java/com/sosauce/cutemusic/ui/screens/all_folders/AllFoldersScreen.kt
index 5208adb..49054bb 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/screens/all_folders/AllFoldersScreen.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/screens/all_folders/AllFoldersScreen.kt
@@ -16,7 +16,6 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
@@ -44,9 +43,8 @@ import androidx.compose.ui.unit.dp
import androidx.media3.common.MediaItem
import com.sosauce.cutemusic.R
import com.sosauce.cutemusic.data.actions.PlayerActions
-import com.sosauce.cutemusic.domain.model.Folder
import com.sosauce.cutemusic.ui.navigation.Screen
-import com.sosauce.cutemusic.ui.screens.blacklisted.components.FolderItem
+import com.sosauce.cutemusic.ui.screens.blacklisted.FolderItem
import com.sosauce.cutemusic.ui.screens.main.MusicListItem
import com.sosauce.cutemusic.ui.shared_components.CuteSearchbar
import com.sosauce.cutemusic.ui.shared_components.CuteText
@@ -118,22 +116,22 @@ fun SharedTransitionScope.AllFoldersScreen(
)
FolderItem(
- folder = Folder(
- name = folder?.substring(folder.lastIndexOf('/') + 1) ?: "No Name",
- path = folder.toString()
- ),
- onClick = { areMusicsVisible[folder ?: "No Name"] = !isExpanded },
+ folder = folder ?: " No name",
topDp = topDp,
bottomDp = bottomDp,
- icon = {
- Icon(
- imageVector = Icons.Rounded.ArrowBackIosNew,
- contentDescription = null,
- modifier = Modifier
- .size(30.dp)
- .rotate(rotation),
- tint = MaterialTheme.colorScheme.onBackground
- )
+ modifier = Modifier.animateItem(),
+ actionButton = {
+ IconButton(
+ onClick = {
+ areMusicsVisible[folder ?: "No name"] = !isExpanded
+ }
+ ) {
+ Icon(
+ imageVector = Icons.Rounded.ArrowBackIosNew,
+ contentDescription = null,
+ modifier = Modifier.rotate(rotation)
+ )
+ }
}
)
}
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/screens/artist/ArtistDetails.kt b/app/src/main/java/com/sosauce/cutemusic/ui/screens/artist/ArtistDetails.kt
index 01893ad..f8e0702 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/screens/artist/ArtistDetails.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/screens/artist/ArtistDetails.kt
@@ -21,7 +21,7 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material.icons.automirrored.rounded.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
@@ -79,7 +79,8 @@ fun SharedTransitionScope.ArtistDetails(
chargePVMAlbumSongs = { postViewModel.albumSongs(it) },
artist = artist,
currentMusicUri = musicState.currentMusicUri,
- isPlayerReady = musicState.isPlayerReady
+ isPlayerReady = musicState.isPlayerReady,
+ animatedVisibilityScope = animatedVisibilityScope
)
} else {
Scaffold(
@@ -96,7 +97,6 @@ fun SharedTransitionScope.ArtistDetails(
)
CuteText(
text = "${artistSongs.size} ${if (artistSongs.size <= 1) "song" else "songs"}",
-
fontSize = 20.sp
)
}
@@ -106,7 +106,7 @@ fun SharedTransitionScope.ArtistDetails(
onClick = navController::navigateUp
) {
Icon(
- imageVector = Icons.AutoMirrored.Filled.ArrowBack,
+ imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
contentDescription = "Back arrow"
)
}
@@ -137,7 +137,8 @@ fun SharedTransitionScope.ArtistDetails(
.clickable {
postViewModel.albumSongs(album.name)
onNavigate(Screen.AlbumsDetails(album.id))
- }
+ },
+ animatedVisibilityScope = animatedVisibilityScope
)
}
}
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/screens/artist/ArtistDetailsLandscape.kt b/app/src/main/java/com/sosauce/cutemusic/ui/screens/artist/ArtistDetailsLandscape.kt
index 2e83c40..1e8e05c 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/screens/artist/ArtistDetailsLandscape.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/screens/artist/ArtistDetailsLandscape.kt
@@ -1,5 +1,10 @@
+@file:OptIn(ExperimentalSharedTransitionApi::class)
+
package com.sosauce.cutemusic.ui.screens.artist
+import androidx.compose.animation.AnimatedVisibilityScope
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.SharedTransitionScope
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -32,7 +37,7 @@ import com.sosauce.cutemusic.ui.screens.main.MusicListItem
import com.sosauce.cutemusic.ui.shared_components.CuteText
@Composable
-fun ArtistDetailsLandscape(
+fun SharedTransitionScope.ArtistDetailsLandscape(
onNavigateUp: () -> Unit,
artistAlbums: List,
artistSongs: List,
@@ -41,7 +46,8 @@ fun ArtistDetailsLandscape(
chargePVMAlbumSongs: (String) -> Unit,
artist: Artist,
currentMusicUri: String,
- isPlayerReady: Boolean
+ isPlayerReady: Boolean,
+ animatedVisibilityScope: AnimatedVisibilityScope,
) {
Column(
modifier = Modifier
@@ -87,7 +93,8 @@ fun ArtistDetailsLandscape(
chargePVMAlbumSongs(album.name)
onNavigate(Screen.AlbumsDetails(album.id))
}
- .size(230.dp)
+ .size(230.dp),
+ animatedVisibilityScope = animatedVisibilityScope
)
}
}
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/screens/blacklisted/BlacklistedScreen.kt b/app/src/main/java/com/sosauce/cutemusic/ui/screens/blacklisted/BlacklistedScreen.kt
index e5d2e55..6d50340 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/screens/blacklisted/BlacklistedScreen.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/screens/blacklisted/BlacklistedScreen.kt
@@ -2,15 +2,12 @@
package com.sosauce.cutemusic.ui.screens.blacklisted
-import android.widget.Toast
import androidx.compose.animation.core.animateDpAsState
-import androidx.compose.animation.core.tween
import androidx.compose.foundation.Image
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@@ -19,27 +16,21 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Add
-import androidx.compose.material.icons.filled.Delete
-import androidx.compose.material.icons.filled.FolderOpen
+import androidx.compose.material.icons.rounded.Add
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -48,7 +39,6 @@ import androidx.navigation.NavController
import com.sosauce.cutemusic.R
import com.sosauce.cutemusic.data.datastore.rememberAllBlacklistedFolders
import com.sosauce.cutemusic.domain.model.Folder
-import com.sosauce.cutemusic.ui.screens.blacklisted.components.AllFoldersBottomSheet
import com.sosauce.cutemusic.ui.shared_components.AppBar
import com.sosauce.cutemusic.ui.shared_components.CuteText
import java.io.File
@@ -58,51 +48,6 @@ fun BlacklistedScreen(
navController: NavController,
folders: List,
) {
- var isSheetOpen by remember { mutableStateOf(false) }
- val context = LocalContext.current
- var blacklistedFolders by rememberAllBlacklistedFolders()
-
- if (isSheetOpen) {
- ModalBottomSheet(
- onDismissRequest = { isSheetOpen = false },
- modifier = Modifier.fillMaxHeight()
- ) {
- AllFoldersBottomSheet(
- folders = folders,
- onClick = { path ->
- if (path in blacklistedFolders) {
- Toast.makeText(
- context,
- context.resources.getText(R.string.alrdy_blacklisted),
- Toast.LENGTH_SHORT
- ).show()
- } else {
- blacklistedFolders = blacklistedFolders.toMutableSet().apply {
- add(path)
- }
- isSheetOpen = false
- Toast.makeText(
- context,
- context.resources.getText(R.string.pls_restart),
- Toast.LENGTH_SHORT
- ).show()
- }
- }
- )
- }
- }
-
- BlacklistedScreenContent(
- onAddFolder = { isSheetOpen = true },
- onPopBackStack = navController::navigateUp
- )
-}
-
-@Composable
-private fun BlacklistedScreenContent(
- onAddFolder: () -> Unit,
- onPopBackStack: () -> Unit,
-) {
var blacklistedFolders by rememberAllBlacklistedFolders()
@@ -111,18 +56,8 @@ private fun BlacklistedScreenContent(
AppBar(
title = stringResource(id = R.string.blacklisted_folders),
showBackArrow = true,
- onPopBackStack = { onPopBackStack() }
+ onPopBackStack = navController::navigateUp
)
- },
- floatingActionButton = {
- FloatingActionButton(
- onClick = { onAddFolder() }
- ) {
- Icon(
- imageVector = Icons.Default.Add,
- contentDescription = null
- )
- }
}
) { values ->
LazyColumn(
@@ -130,54 +65,97 @@ private fun BlacklistedScreenContent(
.fillMaxSize()
.padding(values)
) {
- itemsIndexed(
- items = blacklistedFolders.toList(),
- key = { _, folder -> folder }
- ) { index, folder ->
+ folders.sortedBy { it.name }
+ .groupBy { it.path in blacklistedFolders }
+ .toSortedMap(compareByDescending { it })
+ .forEach { (isBlacklisted, allFolders) ->
+ item {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(
+ horizontal = 34.dp,
+ vertical = 8.dp
+ )
+ ) {
+ CuteText(
+ text = if (isBlacklisted) stringResource(R.string.blacklisted) else stringResource(
+ R.string.not_blacklisted
+ ),
+ color = MaterialTheme.colorScheme.primary
+ )
+ }
+ }
- val topDp by animateDpAsState(
- targetValue = if (index == 0) 24.dp else 4.dp,
- label = "Top Dp",
- animationSpec = tween(500)
- )
- val bottomDp by animateDpAsState(
- targetValue = if (index == blacklistedFolders.size - 1) 24.dp else 4.dp,
- label = "Bottom Dp",
- animationSpec = tween(500)
- )
+ itemsIndexed(
+ items = allFolders,
+ key = { _, folder -> folder.path }
+ ) { index, folder ->
+ val topDp by animateDpAsState(
+ targetValue = if (index == 0) 24.dp else 4.dp,
+ label = "Top Dp"
+ )
+ val bottomDp by animateDpAsState(
+ targetValue = if (index == allFolders.size - 1) 24.dp else 4.dp,
+ label = "Bottom Dp"
+ )
- BlackFolderItem(
- folder = folder,
- onClick = {
- blacklistedFolders = blacklistedFolders.toMutableSet().apply {
- remove(folder)
- }
- },
- topDp = topDp,
- bottomDp = bottomDp,
- modifier = Modifier.animateItem()
- )
- }
+ FolderItem(
+ folder = folder.path,
+ topDp = topDp,
+ bottomDp = bottomDp,
+ modifier = Modifier.animateItem(),
+ actionButton = {
+ if (isBlacklisted) {
+ IconButton(
+ onClick = {
+ blacklistedFolders =
+ blacklistedFolders.toMutableSet().apply {
+ remove(folder.path)
+ }
+ }
+ ) {
+ Icon(
+ painter = painterResource(R.drawable.trash_rounded_filled),
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.error
+ )
+ }
+ } else {
+ IconButton(
+ onClick = {
+ blacklistedFolders =
+ blacklistedFolders.toMutableSet().apply {
+ add(folder.path)
+ }
+ }
+ ) {
+ Icon(
+ imageVector = Icons.Rounded.Add,
+ contentDescription = null
+ )
+ }
+ }
+ }
+ )
+ }
+ }
}
}
}
@Composable
-private fun BlackFolderItem(
+fun FolderItem(
modifier: Modifier = Modifier,
folder: String,
- onClick: () -> Unit,
topDp: Dp,
bottomDp: Dp,
+ actionButton: @Composable () -> Unit
) {
Card(
modifier = modifier
- .padding(
- start = 13.dp,
- end = 13.dp,
- bottom = 8.dp
- ),
+ .padding(horizontal = 16.dp, vertical = 2.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainer
),
@@ -196,7 +174,7 @@ private fun BlackFolderItem(
horizontalArrangement = Arrangement.SpaceBetween
) {
Image(
- imageVector = Icons.Default.FolderOpen,
+ painter = painterResource(R.drawable.folder_rounded),
contentDescription = null,
modifier = Modifier.size(33.dp),
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onBackground)
@@ -208,7 +186,7 @@ private fun BlackFolderItem(
horizontalAlignment = Alignment.Start
) {
CuteText(
- text = getFileName(folder),
+ text = File(folder).name,
fontSize = 18.sp
)
CuteText(
@@ -218,20 +196,7 @@ private fun BlackFolderItem(
modifier = Modifier.basicMarquee()
)
}
- IconButton(
- onClick = onClick
- ) {
- Icon(
- imageVector = Icons.Default.Delete,
- contentDescription = null,
- tint = MaterialTheme.colorScheme.error
- )
- }
+ actionButton()
}
}
-}
-
-private fun getFileName(filePath: String): String {
- val file = File(filePath)
- return file.name
}
\ No newline at end of file
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/screens/blacklisted/components/AllFolderBottomSheet.kt b/app/src/main/java/com/sosauce/cutemusic/ui/screens/blacklisted/components/AllFolderBottomSheet.kt
deleted file mode 100644
index 8b4704d..0000000
--- a/app/src/main/java/com/sosauce/cutemusic/ui/screens/blacklisted/components/AllFolderBottomSheet.kt
+++ /dev/null
@@ -1,105 +0,0 @@
-package com.sosauce.cutemusic.ui.screens.blacklisted.components
-
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.itemsIndexed
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.rounded.Folder
-import androidx.compose.material3.Card
-import androidx.compose.material3.CardDefaults
-import androidx.compose.material3.Icon
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import com.sosauce.cutemusic.domain.model.Folder
-import com.sosauce.cutemusic.ui.shared_components.CuteText
-
-@Composable
-fun AllFoldersBottomSheet(
- folders: List,
- onClick: (path: String) -> Unit,
-) {
-
- LazyColumn {
- itemsIndexed(
- items = folders,
- key = { _, folder -> folder.path }
- ) { index, folder ->
- FolderItem(
- folder = folder,
- onClick = { path ->
- onClick(path)
- },
- topDp = if (index == 0) 24.dp else 4.dp,
- bottomDp = if (index == folders.size - 1) 24.dp else 4.dp,
- icon = {
- Icon(
- imageVector = Icons.Rounded.Folder,
- contentDescription = null,
- modifier = Modifier.size(33.dp),
- tint = MaterialTheme.colorScheme.onBackground
- )
- }
- )
- }
- }
-}
-
-
-@Composable
-fun FolderItem(
- folder: Folder,
- onClick: (path: String) -> Unit,
- topDp: Dp,
- bottomDp: Dp,
- icon: @Composable () -> Unit
-) {
- Card(
- colors = CardDefaults.cardColors(
- containerColor = MaterialTheme.colorScheme.surfaceContainerHighest.copy(
- alpha = 0.5f
- )
- ),
- modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp),
- shape = RoundedCornerShape(
- topStart = topDp,
- topEnd = topDp,
- bottomStart = bottomDp,
- bottomEnd = bottomDp
- ),
- onClick = { onClick(folder.path) }
- ) {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .padding(10.dp),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.SpaceBetween
- ) {
- icon()
- Column(
- verticalArrangement = Arrangement.Center,
- modifier = Modifier
- .fillMaxWidth()
- .padding(start = 10.dp)
- ) {
- CuteText(
- text = folder.name
- )
- CuteText(
- text = folder.path,
- color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.5f)
- )
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/screens/main/MainScreen.kt b/app/src/main/java/com/sosauce/cutemusic/ui/screens/main/MainScreen.kt
index 2bf51b1..49d250d 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/screens/main/MainScreen.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/screens/main/MainScreen.kt
@@ -7,6 +7,7 @@ package com.sosauce.cutemusic.ui.screens.main
import android.app.Activity
import android.content.Intent
+import android.graphics.Paint
import android.net.Uri
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
@@ -65,7 +66,11 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.draw.rotate
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.nativeCanvas
+import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
@@ -190,10 +195,9 @@ fun SharedTransitionScope.MainScreen(
onChargeAlbumSongs = onChargeAlbumSongs,
onChargeArtistLists = onChargeArtistLists,
modifier = Modifier
- .thenIf(
- index == 0,
+ .thenIf(index == 0) {
Modifier.statusBarsPadding()
- ),
+ },
isPlayerReady = isPlayerReady
)
}
@@ -317,7 +321,9 @@ fun MusicListItem(
onDeleteMusic: (List, ActivityResultLauncher) -> Unit = { _, _ -> },
onChargeAlbumSongs: (String) -> Unit = {},
onChargeArtistLists: (String) -> Unit = {},
- isPlayerReady: Boolean
+ isPlayerReady: Boolean,
+ onDeleteSafTrack: () -> Unit = {},
+ showTrackNumber: Boolean = false
) {
val context = LocalContext.current
@@ -336,6 +342,8 @@ fun MusicListItem(
label = "Background Color",
animationSpec = tween(500)
)
+ val materialSurfaceContainer = MaterialTheme.colorScheme.surfaceContainer
+ val materialOnSurface = MaterialTheme.colorScheme.onSurface
val deleteSongLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
@@ -397,7 +405,51 @@ fun MusicListItem(
stringResource(R.string.artwork),
modifier = Modifier
.padding(start = 10.dp)
- .size(45.dp),
+ .size(45.dp)
+ .drawWithContent {
+ drawContent()
+ if (showTrackNumber && music.mediaMetadata.trackNumber != null && music.mediaMetadata.trackNumber != 0) {
+ val circleCenter = Offset(size.width, size.height / 12)
+ drawCircle(
+ color = materialSurfaceContainer,
+ center = circleCenter,
+ radius = 25f
+ )
+ val text = Paint().apply {
+ color = materialOnSurface.toArgb()
+ textSize = 30f
+ textAlign = Paint.Align.CENTER
+ }
+ drawContext.canvas.nativeCanvas.drawText(
+ music.mediaMetadata.trackNumber.toString(),
+ circleCenter.x,
+ circleCenter.y - (text.ascent() + text.descent()) / 2,
+ text
+ )
+ }
+ },
+// .thenIf(showTrackNumber && music.mediaMetadata.trackNumber != null && music.mediaMetadata.trackNumber != 0) {
+// Modifier.drawWithContent {
+// val circleCenter = Offset(size.width, size.height / 12)
+// drawContent()
+// drawCircle(
+// color = materialSurfaceContainer,
+// center = circleCenter,
+// radius = 25f
+// )
+// val text = Paint().apply {
+// color = materialOnSurface.toArgb()
+// textSize = 30f
+// textAlign = Paint.Align.CENTER
+// }
+// drawContext.canvas.nativeCanvas.drawText(
+// music.mediaMetadata.trackNumber.toString(),
+// circleCenter.x,
+// circleCenter.y - (text.ascent() + text.descent()) / 2,
+// text
+// )
+// }
+// },
contentScale = ContentScale.Crop,
)
@@ -445,62 +497,64 @@ fun MusicListItem(
)
}
)
- DropdownMenuItem(
- onClick = {
- isDropDownExpanded = false
- onLoadMetadata(path ?: "", uri)
- onNavigate(Screen.MetadataEditor(music.mediaId))
- },
- text = {
- CuteText(stringResource(R.string.edit))
- },
- leadingIcon = {
- Icon(
- painter = painterResource(R.drawable.edit_rounded),
- contentDescription = null
- )
- }
- )
- DropdownMenuItem(
- onClick = {
- isDropDownExpanded = false
- onChargeAlbumSongs(music.mediaMetadata.albumTitle.toString())
- onNavigate(
- Screen.AlbumsDetails(
- music.mediaMetadata.extras?.getLong("album_id") ?: 0
+ if (music.mediaMetadata.extras?.getBoolean("is_saf") == false) {
+ DropdownMenuItem(
+ onClick = {
+ isDropDownExpanded = false
+ onLoadMetadata(path ?: "", uri)
+ onNavigate(Screen.MetadataEditor(music.mediaId))
+ },
+ text = {
+ CuteText(stringResource(R.string.edit))
+ },
+ leadingIcon = {
+ Icon(
+ painter = painterResource(R.drawable.edit_rounded),
+ contentDescription = null
)
- )
- },
- text = {
- CuteText(stringResource(R.string.go_to) + music.mediaMetadata.albumTitle)
- },
- leadingIcon = {
- Icon(
- painter = painterResource(androidx.media3.session.R.drawable.media3_icon_album),
- contentDescription = null
- )
- }
- )
- DropdownMenuItem(
- onClick = {
- isDropDownExpanded = false
- onChargeArtistLists(music.mediaMetadata.artist.toString())
- onNavigate(
- Screen.ArtistsDetails(
- music.mediaMetadata.extras?.getLong("artist_id") ?: 0
+ }
+ )
+ DropdownMenuItem(
+ onClick = {
+ isDropDownExpanded = false
+ onChargeAlbumSongs(music.mediaMetadata.albumTitle.toString())
+ onNavigate(
+ Screen.AlbumsDetails(
+ music.mediaMetadata.extras?.getLong("album_id") ?: 0
+ )
)
- )
- },
- text = {
- CuteText(stringResource(R.string.go_to) + music.mediaMetadata.artist)
- },
- leadingIcon = {
- Icon(
- painter = painterResource(R.drawable.artist_rounded),
- contentDescription = null
- )
- }
- )
+ },
+ text = {
+ CuteText("${stringResource(R.string.go_to)} ${music.mediaMetadata.albumTitle}")
+ },
+ leadingIcon = {
+ Icon(
+ painter = painterResource(androidx.media3.session.R.drawable.media3_icon_album),
+ contentDescription = null
+ )
+ }
+ )
+ DropdownMenuItem(
+ onClick = {
+ isDropDownExpanded = false
+ onChargeArtistLists(music.mediaMetadata.artist.toString())
+ onNavigate(
+ Screen.ArtistsDetails(
+ music.mediaMetadata.extras?.getLong("artist_id") ?: 0
+ )
+ )
+ },
+ text = {
+ CuteText("${stringResource(R.string.go_to)} ${music.mediaMetadata.artist}")
+ },
+ leadingIcon = {
+ Icon(
+ painter = painterResource(R.drawable.artist_rounded),
+ contentDescription = null
+ )
+ }
+ )
+ }
DropdownMenuItem(
onClick = {
val shareIntent = Intent().apply {
@@ -530,7 +584,13 @@ fun MusicListItem(
}
)
DropdownMenuItem(
- onClick = { onDeleteMusic(listOf(uri), deleteSongLauncher) },
+ onClick = {
+ if (music.mediaMetadata.extras?.getBoolean("is_saf") == false) {
+ onDeleteMusic(listOf(uri), deleteSongLauncher)
+ } else {
+ onDeleteSafTrack()
+ }
+ },
text = {
CuteText(
text = stringResource(R.string.delete),
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/screens/metadata/MetadataEditor.kt b/app/src/main/java/com/sosauce/cutemusic/ui/screens/metadata/MetadataEditor.kt
index dca8e60..367d81d 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/screens/metadata/MetadataEditor.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/screens/metadata/MetadataEditor.kt
@@ -59,30 +59,15 @@ fun MetadataEditor(
val metadataState by metadataViewModel.metadataState.collectAsStateWithLifecycle()
- MetadataEditorContent(
- music = music,
- onPopBackStack = onPopBackStack,
- onNavigate = onNavigate,
- metadataState = metadataState,
- onMetadataAction = { metadataViewModel.onHandleMetadataActions(it) },
- onEditMusic = onEditMusic
- )
-}
-
-@Composable
-fun MetadataEditorContent(
- music: MediaItem,
- onPopBackStack: () -> Unit,
- onNavigate: (Screen) -> Unit,
- metadataState: MetadataState,
- onMetadataAction: (MetadataActions) -> Unit,
- onEditMusic: (List, ActivityResultLauncher) -> Unit
-) {
val context = LocalContext.current
val uri = Uri.parse(music.mediaMetadata.extras?.getString("uri"))
val photoPickerLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) {
- onMetadataAction(MetadataActions.UpdateAudioArt(it ?: Uri.EMPTY))
+ metadataViewModel.onHandleMetadataActions(
+ MetadataActions.UpdateAudioArt(
+ it ?: Uri.EMPTY
+ )
+ )
}
val editSongLauncher =
@@ -90,7 +75,7 @@ fun MetadataEditorContent(
contract = ActivityResultContracts.StartIntentSenderForResult()
) {
if (it.resultCode == Activity.RESULT_OK) {
- onMetadataAction(MetadataActions.SaveChanges)
+ metadataViewModel.onHandleMetadataActions(MetadataActions.SaveChanges)
Toast.makeText(
context,
context.getString(R.string.success),
@@ -273,6 +258,7 @@ fun MetadataEditorContent(
}
}
+
@Composable
private fun EditTextField(
modifier: Modifier = Modifier,
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/NowPlayingLandscape.kt b/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/NowPlayingLandscape.kt
index 36d12a1..2753e06 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/NowPlayingLandscape.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/NowPlayingLandscape.kt
@@ -178,7 +178,7 @@ fun SharedTransitionScope.NowPlayingLandscape(
onChargeAlbumSongs = onChargeAlbumSongs,
onShowSpeedCard = { showSpeedCard = true },
onChargeArtistLists = onChargeArtistLists,
- onHandlePlayerActions = onEvent
+ onHandlePlayerActions = onEvent,
)
}
}
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/NowPlayingScreen.kt b/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/NowPlayingScreen.kt
index 439ea99..4b14079 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/NowPlayingScreen.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/NowPlayingScreen.kt
@@ -6,7 +6,6 @@ package com.sosauce.cutemusic.ui.screens.playing
import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionScope
-import androidx.compose.animation.core.tween
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -153,7 +152,7 @@ private fun SharedTransitionScope.NowPlayingContent(
horizontalArrangement = Arrangement.Start
) {
IconButton(
- onClick = onNavigateUp
+ onClick = onNavigateUp,
) {
Icon(
imageVector = Icons.Rounded.KeyboardArrowDown,
@@ -193,14 +192,12 @@ private fun SharedTransitionScope.NowPlayingContent(
color = MaterialTheme.colorScheme.onBackground,
fontSize = 20.sp,
modifier = Modifier
- .sharedElement(
- state = rememberSharedContentState(key = "currentlyPlaying"),
- animatedVisibilityScope = animatedVisibilityScope,
- boundsTransform = { _, _ ->
- tween(durationMillis = 500)
- }
- )
.basicMarquee()
+ .sharedBounds(
+ sharedContentState = rememberSharedContentState(key = "currentlyPlaying"),
+ animatedVisibilityScope = animatedVisibilityScope
+ )
+
)
//Spacer(modifier = Modifier.height(5.dp))
CuteText(
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/components/Buttons.kt b/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/components/Buttons.kt
index 015fb3f..9888703 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/components/Buttons.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/components/Buttons.kt
@@ -219,10 +219,7 @@ fun SharedTransitionScope.ActionsButtonsRow(
}
.sharedElement(
state = rememberSharedContentState(key = "skipPreviousButton"),
- animatedVisibilityScope = animatedVisibilityScope,
- boundsTransform = { _, _ ->
- tween(durationMillis = 500)
- }
+ animatedVisibilityScope = animatedVisibilityScope
)
)
} else {
@@ -269,10 +266,7 @@ fun SharedTransitionScope.ActionsButtonsRow(
contentDescription = "pause/play button",
modifier = Modifier.sharedElement(
state = rememberSharedContentState(key = "playPauseIcon"),
- animatedVisibilityScope = animatedVisibilityScope,
- boundsTransform = { _, _ ->
- tween(durationMillis = 500)
- }
+ animatedVisibilityScope = animatedVisibilityScope
)
)
}
@@ -349,10 +343,7 @@ fun SharedTransitionScope.ActionsButtonsRow(
}
.sharedElement(
state = rememberSharedContentState(key = "skipNextButton"),
- animatedVisibilityScope = animatedVisibilityScope,
- boundsTransform = { _, _ ->
- tween(durationMillis = 500)
- }
+ animatedVisibilityScope = animatedVisibilityScope
)
)
}
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/components/QuickActionsRow.kt b/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/components/QuickActionsRow.kt
index 383d952..260db19 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/components/QuickActionsRow.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/components/QuickActionsRow.kt
@@ -1,7 +1,13 @@
package com.sosauce.cutemusic.ui.screens.playing.components
+import android.content.Context
import android.content.Intent
+import android.media.audiofx.AudioEffect
import android.net.Uri
+import android.util.Log
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContract
+import androidx.activity.result.launch
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -12,11 +18,13 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.Article
+import androidx.compose.material.icons.automirrored.rounded.OpenInNew
import androidx.compose.material.icons.rounded.ErrorOutline
import androidx.compose.material.icons.rounded.MoreVert
import androidx.compose.material.icons.rounded.Speed
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@@ -39,6 +47,7 @@ import com.sosauce.cutemusic.data.actions.PlayerActions
import com.sosauce.cutemusic.ui.navigation.Screen
import com.sosauce.cutemusic.ui.shared_components.CuteText
import com.sosauce.cutemusic.ui.shared_components.MusicStateDetailsDialog
+import com.sosauce.cutemusic.utils.CUTE_MUSIC_ID
@Composable
fun QuickActionsRow(
@@ -48,7 +57,7 @@ fun QuickActionsRow(
onChargeAlbumSongs: (String) -> Unit,
onNavigate: (Screen) -> Unit,
onChargeArtistLists: (String) -> Unit,
- onHandlePlayerActions: (PlayerActions) -> Unit
+ onHandlePlayerActions: (PlayerActions) -> Unit,
) {
val context = LocalContext.current
var isDropDownExpanded by remember { mutableStateOf(false) }
@@ -56,6 +65,7 @@ fun QuickActionsRow(
val uri = remember { Uri.parse(musicState.currentMusicUri) }
var showTimePicker by remember { mutableStateOf(false) }
val onBackground = MaterialTheme.colorScheme.onBackground
+ val eqIntent = rememberLauncherForActivityResult(equalizerActivityContract()) { }
if (showDetailsDialog) {
MusicStateDetailsDialog(
@@ -131,6 +141,32 @@ fun QuickActionsRow(
onDismissRequest = { isDropDownExpanded = false },
shape = RoundedCornerShape(24.dp)
) {
+ DropdownMenuItem(
+ onClick = {
+ try {
+ eqIntent.launch()
+ } catch (e: Exception) {
+ Log.d(
+ "CuteError",
+ "Couldn't open EQ: ${e.stackTrace}, ${e.message}"
+ )
+ }
+ },
+ text = {
+ CuteText(stringResource(R.string.open_eq))
+ },
+ leadingIcon = {
+ Icon(
+ imageVector = Icons.AutoMirrored.Rounded.OpenInNew,
+ contentDescription = null
+ )
+ }
+ )
+ HorizontalDivider(
+ modifier = Modifier
+ .fillMaxWidth(0.9f)
+ .align(Alignment.CenterHorizontally)
+ )
DropdownMenuItem(
onClick = { showDetailsDialog = true },
text = {
@@ -151,7 +187,7 @@ fun QuickActionsRow(
onNavigate(Screen.AlbumsDetails(musicState.currentAlbumId))
},
text = {
- CuteText(stringResource(R.string.go_to) + musicState.currentAlbum)
+ CuteText("${stringResource(R.string.go_to)} ${musicState.currentAlbum}")
},
leadingIcon = {
Icon(
@@ -167,7 +203,7 @@ fun QuickActionsRow(
onNavigate(Screen.ArtistsDetails(musicState.currentArtistId))
},
text = {
- CuteText(stringResource(R.string.go_to) + musicState.currentArtist)
+ CuteText("${stringResource(R.string.go_to)} ${musicState.currentArtist}")
},
leadingIcon = {
Icon(
@@ -205,4 +241,21 @@ fun QuickActionsRow(
}
}
}
+}
+
+private fun equalizerActivityContract() = object : ActivityResultContract() {
+ override fun createIntent(
+ context: Context,
+ input: Unit,
+ ) = Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL).apply {
+ putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.packageName)
+ putExtra(AudioEffect.EXTRA_AUDIO_SESSION, CUTE_MUSIC_ID)
+ putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC)
+ }
+
+ override fun parseResult(
+ resultCode: Int,
+ intent: Intent?,
+ ) {
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/screens/saf/SafScreen.kt b/app/src/main/java/com/sosauce/cutemusic/ui/screens/saf/SafScreen.kt
new file mode 100644
index 0000000..27f4f37
--- /dev/null
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/screens/saf/SafScreen.kt
@@ -0,0 +1,152 @@
+package com.sosauce.cutemusic.ui.screens.saf
+
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContract
+import androidx.activity.result.launch
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.rounded.OpenInNew
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import androidx.media3.common.MediaItem
+import com.sosauce.cutemusic.R
+import com.sosauce.cutemusic.data.datastore.rememberAllSafTracks
+import com.sosauce.cutemusic.ui.navigation.Screen
+import com.sosauce.cutemusic.ui.screens.main.MusicListItem
+import com.sosauce.cutemusic.ui.shared_components.AppBar
+import com.sosauce.cutemusic.ui.shared_components.CuteText
+
+@Composable
+fun SafScreen(
+ onNavigateUp: () -> Unit,
+ latestSafTracks: List,
+ onNavigate: (Screen) -> Unit,
+ onShortClick: (String) -> Unit,
+ isPlayerReady: Boolean,
+ currentMusicUri: String,
+) {
+
+ val context = LocalContext.current
+ var safTracks by rememberAllSafTracks()
+
+ val safAudioPicker = rememberLauncherForActivityResult(safActivityContract()) {
+ safTracks = safTracks.toMutableSet().apply {
+ add(it.toString())
+ }
+
+ context.contentResolver.takePersistableUriPermission(
+ it ?: Uri.EMPTY,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION or
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ )
+ }
+
+ Scaffold(
+ topBar = {
+ AppBar(
+ title = stringResource(R.string.saf_manager),
+ showBackArrow = true,
+ onPopBackStack = onNavigateUp
+ )
+ }
+ ) { values ->
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(values)
+ ) {
+ Card(
+ colors = CardDefaults.cardColors(MaterialTheme.colorScheme.surfaceContainer),
+ modifier = Modifier.align(Alignment.CenterHorizontally),
+ onClick = {
+ safAudioPicker.launch()
+ }
+ ) {
+ Row(
+ modifier = Modifier.padding(10.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Rounded.OpenInNew,
+ contentDescription = null
+ )
+ Spacer(Modifier.width(10.dp))
+ CuteText(stringResource(R.string.open_saf))
+ }
+ }
+ Spacer(Modifier.height(10.dp))
+ LazyColumn {
+ items(
+ items = latestSafTracks.toList(),
+ key = { it.mediaId }
+ ) { safTrack ->
+
+ Column(
+ modifier = Modifier
+ .animateItem()
+ .padding(
+ vertical = 2.dp,
+ horizontal = 4.dp
+ )
+ ) {
+ MusicListItem(
+ onShortClick = { onShortClick(safTrack.mediaId) },
+ music = safTrack,
+ onNavigate = { onNavigate(it) },
+ currentMusicUri = currentMusicUri,
+ showBottomSheet = true,
+ isPlayerReady = isPlayerReady,
+ onDeleteSafTrack = {
+ safTracks = safTracks.toMutableSet().apply {
+ remove(safTrack.mediaMetadata.extras?.getString("uri"))
+ }
+ }
+ )
+ }
+
+ }
+ }
+ }
+
+
+ }
+
+}
+
+private fun safActivityContract() = object : ActivityResultContract() {
+ override fun createIntent(
+ context: Context,
+ input: Unit,
+ ) = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
+ addCategory(Intent.CATEGORY_OPENABLE)
+ type = "audio/*"
+ }
+
+ override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
+ return intent?.data
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/screens/settings/compenents/SettingsCards.kt b/app/src/main/java/com/sosauce/cutemusic/ui/screens/settings/compenents/SettingsCards.kt
index e871579..8c77399 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/screens/settings/compenents/SettingsCards.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/screens/settings/compenents/SettingsCards.kt
@@ -27,15 +27,15 @@ import androidx.compose.ui.unit.sp
import com.sosauce.cutemusic.ui.shared_components.CuteText
@Composable
-inline fun SettingsCards(
+fun SettingsCards(
hasInfoDialog: Boolean = false,
checked: Boolean,
topDp: Dp,
bottomDp: Dp,
text: String,
- crossinline onCheckedChange: () -> Unit,
- crossinline onClick: () -> Unit = {},
- crossinline optionalDescription: @Composable () -> Unit = {}
+ onCheckedChange: () -> Unit,
+ onClick: () -> Unit = {},
+ optionalDescription: @Composable () -> Unit = {}
) {
Card(
colors = CardDefaults.cardColors(MaterialTheme.colorScheme.surfaceContainer),
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/screens/settings/compenents/Switches.kt b/app/src/main/java/com/sosauce/cutemusic/ui/screens/settings/compenents/Switches.kt
index 0edf924..8e95dde 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/screens/settings/compenents/Switches.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/screens/settings/compenents/Switches.kt
@@ -16,6 +16,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.sosauce.cutemusic.R
@@ -36,6 +37,7 @@ fun Misc(
onNavigate: (Screen) -> Unit
) {
//var killService by remember { rememberKillService(context) }
+ val context = LocalContext.current
Column {
CuteText(
@@ -56,6 +58,19 @@ fun Misc(
topDp = 24.dp,
bottomDp = 24.dp
)
+// TextSettingsCards(
+// text = stringResource(id = R.string.saf_manager),
+// onClick = { onNavigate(Screen.Saf) },
+// modifier = Modifier
+// .padding(
+// top = 25.dp,
+// start = 15.dp,
+// bottom = 25.dp
+// )
+// .fillMaxWidth(),
+// topDp = 4.dp,
+// bottomDp = 24.dp
+// )
// SettingsCards(
// checked = killService,
// onCheckedChange = { killService = !killService },
@@ -87,7 +102,8 @@ fun ThemeManagement() {
text = stringResource(id = R.string.follow_sys)
)
AnimatedContent(
- targetState = !followSys, label = "",
+ targetState = !followSys,
+ label = "",
transitionSpec = {
(slideInHorizontally() + fadeIn()).togetherWith(slideOutHorizontally() + fadeOut())
}
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/shared_components/MusicDetailsDialog.kt b/app/src/main/java/com/sosauce/cutemusic/ui/shared_components/MusicDetailsDialog.kt
index 678c6d3..00e0073 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/shared_components/MusicDetailsDialog.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/shared_components/MusicDetailsDialog.kt
@@ -117,6 +117,20 @@ fun MusicDetailsDialog(
text = "${stringResource(id = R.string.duration)}: ${music.mediaMetadata.durationMs?.formatToReadableTime() ?: 0}",
modifier = Modifier.padding(bottom = 5.dp)
)
+ if (music.mediaMetadata.extras?.getBoolean("is_saf") == true) {
+ Spacer(Modifier.height(5.dp))
+ Card(
+ colors = CardDefaults.cardColors(MaterialTheme.colorScheme.surfaceContainerHighest),
+ modifier = Modifier.align(Alignment.CenterHorizontally)
+ ) {
+ Row(
+ modifier = Modifier.padding(10.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ CuteText(stringResource(R.string.from_saf))
+ }
+ }
+ }
}
}
)
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/shared_components/MusicViewModel.kt b/app/src/main/java/com/sosauce/cutemusic/ui/shared_components/MusicViewModel.kt
index 39caa7b..7b2235a 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/shared_components/MusicViewModel.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/shared_components/MusicViewModel.kt
@@ -29,6 +29,7 @@ import com.sosauce.cutemusic.data.MusicState
import com.sosauce.cutemusic.data.actions.PlayerActions
import com.sosauce.cutemusic.domain.model.Lyrics
import com.sosauce.cutemusic.domain.repository.MediaStoreHelper
+import com.sosauce.cutemusic.domain.repository.SafManager
import com.sosauce.cutemusic.main.PlaybackService
import com.sosauce.cutemusic.utils.applyLoop
import com.sosauce.cutemusic.utils.applyPlaybackSpeed
@@ -37,16 +38,24 @@ import com.sosauce.cutemusic.utils.playAtIndex
import com.sosauce.cutemusic.utils.playFromAlbum
import com.sosauce.cutemusic.utils.playFromArtist
import com.sosauce.cutemusic.utils.playRandom
+import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import java.io.File
import java.io.FileNotFoundException
+@OptIn(FlowPreview::class)
+@SuppressLint("UnsafeOptInUsageError")
class MusicViewModel(
private val application: Application,
- private val mediaStoreHelper: MediaStoreHelper
+ private val mediaStoreHelper: MediaStoreHelper,
+ private val safManager: SafManager
) : AndroidViewModel(application) {
private var mediaController: MediaController? by mutableStateOf(null)
@@ -58,7 +67,8 @@ class MusicViewModel(
var sleepCountdownTimer: CountDownTimer? = null
- private val playerListener = object : Player.Listener {
+ private val playerListener = @UnstableApi
+ object : Player.Listener {
@UnstableApi
override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) {
super.onMediaMetadataChanged(mediaMetadata)
@@ -67,8 +77,8 @@ class MusicViewModel(
currentArtist = mediaMetadata.artist.toString(),
currentArtistId = mediaMetadata.extras?.getLong("artist_id") ?: 0,
currentArt = mediaMetadata.artworkUri,
- currentPath = mediaMetadata.extras?.getString("path") ?: "No Path Found!",
- currentMusicUri = mediaMetadata.extras?.getString("uri") ?: "No Uri Found!",
+ currentPath = mediaMetadata.extras?.getString("path") ?: "No path found!",
+ currentMusicUri = mediaMetadata.extras?.getString("uri") ?: "No uri found!",
currentLrcFile = getLrcFile(),
currentAlbum = mediaMetadata.albumTitle.toString(),
currentAlbumId = mediaMetadata.extras?.getLong("album_id") ?: 0,
@@ -81,46 +91,58 @@ class MusicViewModel(
override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters) {
super.onPlaybackParametersChanged(playbackParameters)
- _musicState.value = _musicState.value.copy(
- playbackParameters = playbackParameters
- )
+ _musicState.update {
+ it.copy(
+ playbackParameters = playbackParameters
+ )
+ }
}
override fun onIsPlayingChanged(isPlaying: Boolean) {
super.onIsPlayingChanged(isPlaying)
- _musicState.value = _musicState.value.copy(
- isCurrentlyPlaying = isPlaying
- )
+ _musicState.update {
+ it.copy(
+ isCurrentlyPlaying = isPlaying
+ )
+ }
}
override fun onRepeatModeChanged(repeatMode: Int) {
super.onRepeatModeChanged(repeatMode)
when (repeatMode) {
Player.REPEAT_MODE_ONE -> {
- _musicState.value = _musicState.value.copy(
- isLooping = true
- )
+ _musicState.update {
+ it.copy(
+ isLooping = true
+ )
+ }
}
Player.REPEAT_MODE_OFF -> {
- _musicState.value = _musicState.value.copy(
- isLooping = false
- )
+ _musicState.update {
+ it.copy(
+ isLooping = false
+ )
+ }
}
else -> {
- _musicState.value = _musicState.value.copy(
- isLooping = false
- )
+ _musicState.update {
+ it.copy(
+ isLooping = false
+ )
+ }
}
}
}
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
super.onShuffleModeEnabledChanged(shuffleModeEnabled)
- _musicState.value = _musicState.value.copy(
- isShuffling = shuffleModeEnabled
- )
+ _musicState.update {
+ it.copy(
+ isShuffling = shuffleModeEnabled
+ )
+ }
}
@@ -128,10 +150,12 @@ class MusicViewModel(
super.onEvents(player, events)
viewModelScope.launch {
while (player.isPlaying) {
- _musicState.value = _musicState.value.copy(
- currentMusicDuration = player.duration,
- currentPosition = player.currentPosition
- )
+ _musicState.update {
+ it.copy(
+ currentMusicDuration = player.duration,
+ currentPosition = player.currentPosition
+ )
+ }
delay(500)
}
}
@@ -141,27 +165,55 @@ class MusicViewModel(
super.onPlaybackStateChanged(playbackState)
when (playbackState) {
Player.STATE_IDLE -> {
- _musicState.value = _musicState.value.copy(
- isPlayerReady = false
- )
+ _musicState.update {
+ it.copy(
+ isPlayerReady = false
+ )
+ }
}
Player.STATE_READY -> {
- _musicState.value = _musicState.value.copy(
- isPlayerReady = true
- )
+ _musicState.update {
+ it.copy(
+ isPlayerReady = true
+ )
+ }
}
else -> {
- _musicState.value = _musicState.value.copy(
- isPlayerReady = true
- )
+ _musicState.update {
+ it.copy(
+ isPlayerReady = true
+ )
+ }
}
}
}
}
init {
+// if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_CAR) {
+// MediaController
+// .Builder(
+// application,
+// SessionToken(
+// application,
+// ComponentName(application, AutoPlaybackService::class.java)
+// )
+// )
+// .buildAsync()
+// .apply {
+// addListener(
+// {
+// mediaController = get()
+// mediaController!!.addListener(playerListener)
+// },
+// MoreExecutors.directExecutor()
+// )
+//
+// }
+// } else {
+// }
MediaController
.Builder(
application,
@@ -176,7 +228,24 @@ class MusicViewModel(
{
mediaController = get()
mediaController!!.addListener(playerListener)
- mediaController!!.setMediaItems(mediaStoreHelper.musics)
+ viewModelScope.launch {
+ combine(
+ mediaStoreHelper.fetchLatestMusics(),
+ safManager.fetchLatestSafTracks()
+ ) { musics, safTracks ->
+ val combinedList = musics + safTracks
+ combinedList
+ }
+ .debounce(500)
+ .collectLatest { combinedList ->
+ mediaController!!.replaceMediaItems(
+ 0,
+ combinedList.size - 1,
+ combinedList
+ )
+ }
+ }
+
},
MoreExecutors.directExecutor()
)
@@ -219,7 +288,8 @@ class MusicViewModel(
val fd = getFileDescriptorFromPath(application, musicState.value.currentPath)
return fd?.dup()?.detachFd()?.let {
- TagLib.getMetadata(it)?.propertyMap["LYRICS"]?.getOrNull(0) ?: application.getString(R.string.no_lyrics_note)
+ TagLib.getMetadata(it)?.propertyMap["LYRICS"]?.getOrNull(0)
+ ?: application.getString(R.string.no_lyrics_note)
} ?: application.getString(R.string.no_lyrics_note)
}
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/shared_components/PostViewModel.kt b/app/src/main/java/com/sosauce/cutemusic/ui/shared_components/PostViewModel.kt
index c912c09..def2896 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/shared_components/PostViewModel.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/shared_components/PostViewModel.kt
@@ -1,5 +1,6 @@
package com.sosauce.cutemusic.ui.shared_components
+import android.annotation.SuppressLint
import android.net.Uri
import android.util.Log
import androidx.activity.result.ActivityResultLauncher
@@ -12,24 +13,40 @@ import androidx.lifecycle.viewModelScope
import androidx.media3.common.MediaItem
import com.sosauce.cutemusic.domain.model.Album
import com.sosauce.cutemusic.domain.repository.MediaStoreHelper
+import com.sosauce.cutemusic.domain.repository.SafManager
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlin.collections.filter
class PostViewModel(
- private val mediaStoreHelper: MediaStoreHelper
+ private val mediaStoreHelper: MediaStoreHelper,
+ private val safManager: SafManager
) : ViewModel() {
+ @SuppressLint("UnsafeOptInUsageError")
+ val safTracks = safManager.fetchLatestSafTracks()
+
+// @SuppressLint("UnsafeOptInUsageError")
+// var musics = combine(safTracks, mediaStoreHelper.fetchLatestMusics()) { safList, trackList ->
+// safList + trackList
+// }.stateIn(
+// CoroutineScope(Dispatchers.IO),
+// SharingStarted.WhileSubscribed(5000),
+// mediaStoreHelper.musics
+// )
+
var musics = mediaStoreHelper.fetchLatestMusics().stateIn(
- viewModelScope,
+ CoroutineScope(Dispatchers.IO),
SharingStarted.WhileSubscribed(5000),
mediaStoreHelper.musics
)
+
var albums = mediaStoreHelper.fetchLatestAlbums().stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5000),
@@ -56,9 +73,7 @@ class PostViewModel(
fun albumSongs(album: String) {
try {
viewModelScope.launch {
- musics.collectLatest {
- albumSongs = it.filter { it.mediaMetadata.albumTitle.toString() == album }
- }
+ albumSongs = musics.value.filter { it.mediaMetadata.albumTitle.toString() == album }
}
} catch (e: Exception) {
Log.e(CUTE_ERROR, e.message, e)
@@ -68,9 +83,7 @@ class PostViewModel(
fun artistSongs(artistName: String) {
try {
viewModelScope.launch {
- musics.collectLatest {
- artistSongs = it.filter { it.mediaMetadata.artist == artistName }
- }
+ artistSongs = musics.value.filter { it.mediaMetadata.artist == artistName }
}
} catch (e: Exception) {
Log.e(CUTE_ERROR, e.message, e)
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/shared_components/Searchbar.kt b/app/src/main/java/com/sosauce/cutemusic/ui/shared_components/Searchbar.kt
index f7ef891..aa66759 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/shared_components/Searchbar.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/shared_components/Searchbar.kt
@@ -93,7 +93,11 @@ fun SharedTransitionScope.CuteSearchbar(
minWidth = 45.dp,
minHeight = 45.dp
)
- .align(Alignment.End),
+ .align(Alignment.End)
+ .sharedBounds(
+ sharedContentState = rememberSharedContentState(key = "fab"),
+ animatedVisibilityScope = animatedVisibilityScope
+ ),
shape = RoundedCornerShape(14.dp)
) {
Icon(
@@ -111,12 +115,11 @@ fun SharedTransitionScope.CuteSearchbar(
color = MaterialTheme.colorScheme.surfaceContainer,
shape = RoundedCornerShape(roundedShape)
)
- .thenIf(
- isPlayerReady,
+ .thenIf(isPlayerReady) {
Modifier.clickable {
onNavigate()
}
- )
+ }
) {
AnimatedVisibility(
visible = isPlayerReady,
@@ -149,15 +152,11 @@ fun SharedTransitionScope.CuteSearchbar(
text = currentlyPlaying,
modifier = Modifier
.padding(start = 5.dp)
- .sharedElement(
- state = rememberSharedContentState(key = "currentlyPlaying"),
- animatedVisibilityScope = animatedVisibilityScope,
- boundsTransform = { _, _ ->
- tween(500)
- }
- )
.basicMarquee()
-
+ .sharedBounds(
+ sharedContentState = rememberSharedContentState(key = "currentlyPlaying"),
+ animatedVisibilityScope = animatedVisibilityScope
+ )
)
}
Row {
@@ -188,10 +187,7 @@ fun SharedTransitionScope.CuteSearchbar(
}
.sharedElement(
state = rememberSharedContentState(key = "skipPreviousButton"),
- animatedVisibilityScope = animatedVisibilityScope,
- boundsTransform = { _, _ ->
- tween(500)
- }
+ animatedVisibilityScope = animatedVisibilityScope
)
)
}
@@ -203,10 +199,7 @@ fun SharedTransitionScope.CuteSearchbar(
contentDescription = null,
modifier = Modifier.sharedElement(
state = rememberSharedContentState(key = "playPauseIcon"),
- animatedVisibilityScope = animatedVisibilityScope,
- boundsTransform = { _, _ ->
- tween(500)
- }
+ animatedVisibilityScope = animatedVisibilityScope
)
)
}
@@ -237,10 +230,7 @@ fun SharedTransitionScope.CuteSearchbar(
}
.sharedElement(
state = rememberSharedContentState(key = "skipNextButton"),
- animatedVisibilityScope = animatedVisibilityScope,
- boundsTransform = { _, _ ->
- tween(500)
- }
+ animatedVisibilityScope = animatedVisibilityScope
)
)
}
diff --git a/app/src/main/java/com/sosauce/cutemusic/utils/Constants.kt b/app/src/main/java/com/sosauce/cutemusic/utils/Constants.kt
new file mode 100644
index 0000000..339a2cb
--- /dev/null
+++ b/app/src/main/java/com/sosauce/cutemusic/utils/Constants.kt
@@ -0,0 +1,4 @@
+package com.sosauce.cutemusic.utils
+
+const val CUTE_MUSIC_ID = "CUTE_MUSIC_ID"
+const val ROOT_ID = "cute_music_root"
\ No newline at end of file
diff --git a/app/src/main/java/com/sosauce/cutemusic/utils/Extensions.kt b/app/src/main/java/com/sosauce/cutemusic/utils/Extensions.kt
index 7f56fa0..0c1c5ff 100644
--- a/app/src/main/java/com/sosauce/cutemusic/utils/Extensions.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/utils/Extensions.kt
@@ -19,17 +19,17 @@ import com.kyant.taglib.PropertyMap
import com.sosauce.cutemusic.data.datastore.rememberIsLandscape
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
+import java.io.File
+import java.io.FileOutputStream
import java.util.Locale
fun Modifier.thenIf(
condition: Boolean,
- modifier: Modifier
+ modifier: Modifier.() -> Modifier
): Modifier {
- return this.then(
- if (condition) {
- modifier
- } else Modifier
- )
+ return if (condition) {
+ this.then(modifier())
+ } else this
}
fun Long.formatBinarySize(): String {
@@ -140,6 +140,18 @@ fun Player.applyPlaybackSpeed(
}
+fun ByteArray.getUriFromByteArray(context: Context): Uri {
+ val albumArtFile = File(context.cacheDir, "album_art_${this.hashCode()}.jpg")
+ try {
+ FileOutputStream(albumArtFile).use { os ->
+ os.write(this)
+ }
+ return Uri.fromFile(albumArtFile)
+ } catch (e: Exception) {
+ return Uri.EMPTY
+ }
+}
+
fun Uri.getBitrate(context: Context): String {
val retriever = MediaMetadataRetriever()
return try {
@@ -221,6 +233,7 @@ fun AudioFileMetadata.toPropertyMap(): PropertyMap {
fun ContentResolver.observe(uri: Uri) = callbackFlow {
val observer = object : ContentObserver(null) {
override fun onChange(selfChange: Boolean) {
+
trySend(selfChange)
}
}
diff --git a/app/src/main/res/drawable/trash_rounded_filled.xml b/app/src/main/res/drawable/trash_rounded_filled.xml
new file mode 100644
index 0000000..c14cfe1
--- /dev/null
+++ b/app/src/main/res/drawable/trash_rounded_filled.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e71948b..9e74932 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -75,4 +75,10 @@
Set sleep timer
Go to:
No lyrics found !
+ Equalizer
+ This track comes from the S.A.F
+ Open S.A.F to add tracks
+ Blacklisted
+ Not blacklisted
+ S.A.F manager
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 759f737..b71e133 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -6,8 +6,8 @@ koinAndroidxStartup = "4.0.0"
kotlin = "2.0.21"
activityCompose = "1.9.3"
coilCompose = "3.0.3"
-composeBom = "2024.11.00"
-composeAnimation = "1.7.5"
+composeBom = "2024.12.01"
+composeAnimation = "1.7.6"
coreKtx = "1.15.0"
coreSplashscreen = "1.0.1"
datastorePreferences = "1.1.1"
@@ -16,7 +16,7 @@ lifecycleViewmodelCompose = "2.8.7"
media3Common = "1.5.0"
media3Exoplayer = "1.5.0"
media3Session = "1.5.0"
-navigationCompose = "2.8.4"
+navigationCompose = "2.8.5"
squigglyslider = "1.0.0"
serialization = "2.0.0"
taglib = "1.0.0-alpha25"