diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index b1e3805..76943c1 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -13,8 +13,8 @@ android {
applicationId = "com.sosauce.cutemusic"
minSdk = 26
targetSdk = 35
- versionCode = 13
- versionName = "2.2.2"
+ versionCode = 14
+ versionName = "2.2.3"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
diff --git a/app/release/baselineProfiles/0/app-release.dm b/app/release/baselineProfiles/0/app-release.dm
index 9d41e42..135c032 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 332f30c..83c4505 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 27051fc..5b69d58 100644
--- a/app/release/output-metadata.json
+++ b/app/release/output-metadata.json
@@ -11,8 +11,8 @@
"type": "SINGLE",
"filters": [],
"attributes": [],
- "versionCode": 12,
- "versionName": "2.2.1",
+ "versionCode": 13,
+ "versionName": "2.2.2",
"outputFile": "app-release.apk"
}
],
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 60ff93a..06a4946 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -63,6 +63,9 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/sosauce/cutemusic/data/MusicState.kt b/app/src/main/java/com/sosauce/cutemusic/data/MusicState.kt
new file mode 100644
index 0000000..cff4672
--- /dev/null
+++ b/app/src/main/java/com/sosauce/cutemusic/data/MusicState.kt
@@ -0,0 +1,24 @@
+package com.sosauce.cutemusic.data
+
+import android.net.Uri
+import com.sosauce.cutemusic.domain.model.Lyrics
+import java.io.File
+
+data class MusicState(
+ var currentlyPlaying: String = "",
+ var currentArtist: String = "",
+ val currentArtistId: Long = 0,
+ var currentArt: Uri? = null,
+ var isCurrentlyPlaying: Boolean = false,
+ var currentPosition: Long = 0L,
+ var currentMusicDuration: Long = 0L,
+ var currentMusicUri: String = "",
+ var currentLyrics: List = listOf(),
+ var isLooping: Boolean = false,
+ var isShuffling: Boolean = false,
+ var currentPath: String = "",
+ val currentAlbum: String = "",
+ val currentAlbumId: Long = 0,
+ val currentSize: Long = 0,
+ val currentLrcFile: File? = null
+)
diff --git a/app/src/main/java/com/sosauce/cutemusic/data/actions/PlayerActions.kt b/app/src/main/java/com/sosauce/cutemusic/data/actions/PlayerActions.kt
index 4698540..c8265ee 100644
--- a/app/src/main/java/com/sosauce/cutemusic/data/actions/PlayerActions.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/data/actions/PlayerActions.kt
@@ -5,7 +5,25 @@ sealed interface PlayerActions {
data object SeekToNextMusic : PlayerActions
data object SeekToPreviousMusic : PlayerActions
data object RestartSong : PlayerActions
+ data object PlayRandom : PlayerActions
+ data object ApplyLoop : PlayerActions
+ data object ApplyShuffle : PlayerActions
data class SeekTo(val position: Long) : PlayerActions
data class SeekToSlider(val position: Long) : PlayerActions
data class RewindTo(val position: Long) : PlayerActions
+ data class StartPlayback(val mediaId: String) : PlayerActions
+ data class StartAlbumPlayback(
+ val albumName: String,
+ val mediaId: String?
+ ) : PlayerActions
+
+ data class StartArtistPlayback(
+ val artistName: String,
+ val mediaId: String?
+ ) : PlayerActions
+
+ data class ApplyPlaybackSpeed(
+ val speed: Float,
+ val pitch: Float
+ ) : PlayerActions
}
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 1d45414..bbaa39b 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
@@ -7,16 +7,17 @@ import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.stringSetPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
+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.KILL_SERVICE
import com.sosauce.cutemusic.data.datastore.PreferencesKeys.SNAP_SPEED_N_PITCH
import com.sosauce.cutemusic.data.datastore.PreferencesKeys.SORT_ORDER
import com.sosauce.cutemusic.data.datastore.PreferencesKeys.SORT_ORDER_ALBUMS
import com.sosauce.cutemusic.data.datastore.PreferencesKeys.SORT_ORDER_ARTISTS
import com.sosauce.cutemusic.data.datastore.PreferencesKeys.USE_AMOLED_MODE
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
@@ -37,6 +38,8 @@ private data object PreferencesKeys {
val SNAP_SPEED_N_PITCH = booleanPreferencesKey("snap_peed_n_pitch")
val KILL_SERVICE = booleanPreferencesKey("kill_service")
val USE_ART_THEME = booleanPreferencesKey("use_art_theme")
+ val APPLY_LOOP = booleanPreferencesKey("apply_loop")
+ val USE_CLASSIC_SLIDER = booleanPreferencesKey("use_classic_slider")
}
@Composable
@@ -83,6 +86,12 @@ fun rememberSnapSpeedAndPitch() =
fun rememberUseArtTheme() =
rememberPreference(key = USE_ART_THEME, defaultValue = false)
-fun rememberKillService(context: Context) =
- rememberNonComposablePreference(key = KILL_SERVICE, defaultValue = true, context = context)
+//fun rememberKillService(context: Context) =
+// rememberNonComposablePreference(key = KILL_SERVICE, defaultValue = true, context = context)
+@Composable
+fun rememberShouldApplyLoop() =
+ rememberPreference(key = APPLY_LOOP, defaultValue = false)
+@Composable
+fun rememberUseClassicSlider() =
+ rememberPreference(key = USE_CLASSIC_SLIDER, defaultValue = false)
diff --git a/app/src/main/java/com/sosauce/cutemusic/data/datastore/SettingsExt.kt b/app/src/main/java/com/sosauce/cutemusic/data/datastore/SettingsExt.kt
index fac26ac..866ab4b 100644
--- a/app/src/main/java/com/sosauce/cutemusic/data/datastore/SettingsExt.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/data/datastore/SettingsExt.kt
@@ -1,21 +1,16 @@
package com.sosauce.cutemusic.data.datastore
-import android.content.Context
import android.content.res.Configuration
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
@@ -49,37 +44,6 @@ fun rememberPreference(
}
}
-fun rememberNonComposablePreference(
- key: Preferences.Key,
- defaultValue: T,
- context: Context
-): MutableState {
- val coroutineScope = CoroutineScope(Dispatchers.Main)
- var state by mutableStateOf(defaultValue)
-
- coroutineScope.launch {
- context.dataStore.data
- .map { preferences -> preferences[key] ?: defaultValue }
- .collect { newValue ->
- state = newValue
- }
- }
-
- return object : MutableState {
- override var value: T
- get() = state
- set(value) {
- coroutineScope.launch {
- context.dataStore.edit {
- it[key] = value
- }
- }
- }
-
- override fun component1() = value
- override fun component2(): (T) -> Unit = { value = it }
- }
-}
@Composable
fun rememberIsLandscape(): Boolean {
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 2c6931a..8a87562 100644
--- a/app/src/main/java/com/sosauce/cutemusic/di/AppModule.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/di/AppModule.kt
@@ -1,11 +1,7 @@
package com.sosauce.cutemusic.di
-import android.content.ComponentName
-import androidx.media3.session.MediaController
-import androidx.media3.session.SessionToken
import com.sosauce.cutemusic.domain.repository.MediaStoreHelper
import com.sosauce.cutemusic.domain.repository.MediaStoreHelperImpl
-import com.sosauce.cutemusic.main.PlaybackService
import com.sosauce.cutemusic.ui.screens.metadata.MetadataViewModel
import com.sosauce.cutemusic.ui.shared_components.MusicViewModel
import com.sosauce.cutemusic.ui.shared_components.PostViewModel
@@ -18,20 +14,11 @@ val appModule = module {
single {
MediaStoreHelperImpl(androidContext())
}
- single {
- MediaController.Builder(
- androidContext(),
- SessionToken(
- androidContext(),
- ComponentName(androidContext(), PlaybackService::class.java)
- )
- ).buildAsync()
- }
viewModel {
PostViewModel(get(), androidApplication())
}
viewModel {
- MusicViewModel(get(), androidApplication())
+ MusicViewModel(androidApplication(), 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 f8f0921..478cf79 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
@@ -10,13 +10,17 @@ import com.sosauce.cutemusic.domain.model.Folder
interface MediaStoreHelper {
- fun getMusics() : List
+ val musics: List
+ val albums: List
+ val artists: List
- fun getAlbums(): List
+ fun fetchMusics(): List
- fun getArtists(): List
+ fun fetchAlbums(): List
- fun getFoldersWithMusics(): List
+ fun fetchArtists(): List
+
+ fun fetchFoldersWithMusics(): List
suspend fun deleteMusics(
uris: 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 8d2d1f7..710ff8f 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
@@ -18,16 +18,18 @@ import com.sosauce.cutemusic.domain.model.Folder
class MediaStoreHelperImpl(
private val context: Context
-): MediaStoreHelper {
+) : MediaStoreHelper {
- override fun getMusics(): List {
+ override fun fetchMusics(): List {
val musics = mutableListOf()
val projection = arrayOf(
MediaStore.Audio.Media._ID,
MediaStore.Audio.Media.TITLE,
MediaStore.Audio.Media.ARTIST,
+ MediaStore.Audio.Media.ARTIST_ID,
MediaStore.Audio.Media.ALBUM,
+ MediaStore.Audio.Media.ALBUM_ID,
MediaStore.Audio.Media.DATA,
MediaStore.Audio.Media.SIZE,
//MediaStore.Audio.Media.IS_FAVORITE,
@@ -45,7 +47,9 @@ class MediaStoreHelperImpl(
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID)
val titleColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE)
val artistColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST)
+ val artistIdColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID)
val albumColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM)
+ val albumIdColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID)
val folderColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA)
val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.SIZE)
//val isFavColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.IS_FAVORITE)
@@ -54,7 +58,9 @@ class MediaStoreHelperImpl(
val id = cursor.getLong(idColumn)
val title = cursor.getString(titleColumn)
val artist = cursor.getString(artistColumn)
+ val artistId = cursor.getLong(artistIdColumn)
val album = cursor.getString(albumColumn)
+ val albumId = cursor.getLong(albumIdColumn)
val filePath = cursor.getString(folderColumn)
val folder = filePath.substring(0, filePath.lastIndexOf('/'))
val size = cursor.getLong(sizeColumn)
@@ -63,36 +69,37 @@ class MediaStoreHelperImpl(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
id
)
- val artUri = ContentUris.appendId(
- MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.buildUpon(), id
- ).appendPath("albumart").build()
+
+ val artUri = Uri.parse("$uri/albumart")
musics.add(
- MediaItem
- .Builder()
- .setUri(uri)
- .setMediaId(id.toString())
- .setMediaMetadata(
- MediaMetadata
- .Builder()
- .setIsBrowsable(false)
- .setIsPlayable(true)
- .setTitle(title)
- .setArtist(artist)
- .setAlbumTitle(album)
- .setArtworkUri(artUri)
- .setExtras(
- Bundle()
- .apply {
- putString("folder", folder)
- putLong("size", size)
- putString("path", filePath)
- putString("uri", uri.toString())
- // putInt("isFavorite", isFavorite)
- }).build()
- )
- .build()
+ MediaItem
+ .Builder()
+ .setUri(uri)
+ .setMediaId(id.toString())
+ .setMediaMetadata(
+ MediaMetadata
+ .Builder()
+ .setIsBrowsable(false)
+ .setIsPlayable(true)
+ .setTitle(title)
+ .setArtist(artist)
+ .setAlbumTitle(album)
+ .setArtworkUri(artUri)
+ .setExtras(
+ Bundle()
+ .apply {
+ putString("folder", folder)
+ putLong("size", size)
+ putString("path", filePath)
+ putString("uri", uri.toString())
+ putLong("album_id", albumId)
+ putLong("artist_id", artistId)
+ // putInt("isFavorite", isFavorite)
+ }).build()
+ )
+ .build()
)
}
}
@@ -101,7 +108,7 @@ class MediaStoreHelperImpl(
}
- override fun getAlbums(): List {
+ override fun fetchAlbums(): List {
val albums = mutableListOf()
val projection = arrayOf(
@@ -136,7 +143,7 @@ class MediaStoreHelperImpl(
return albums
}
- override fun getArtists(): List {
+ override fun fetchArtists(): List {
val artists = mutableListOf()
val projection = arrayOf(
@@ -171,7 +178,7 @@ class MediaStoreHelperImpl(
// Only gets folder with musics in them
- override fun getFoldersWithMusics(): List {
+ override fun fetchFoldersWithMusics(): List {
val folders = mutableListOf()
@@ -245,4 +252,9 @@ class MediaStoreHelperImpl(
intentSenderLauncher.launch(IntentSenderRequest.Builder(intentSender).build())
}
}
+
+ // Caching music to not re-query them in Music and Post ViewModels
+ override val musics: List = fetchMusics()
+ override val albums: List = fetchAlbums()
+ override val artists: List = fetchArtists()
}
diff --git a/app/src/main/java/com/sosauce/cutemusic/main/AppContainer.kt b/app/src/main/java/com/sosauce/cutemusic/main/AppContainer.kt
deleted file mode 100644
index 38df531..0000000
--- a/app/src/main/java/com/sosauce/cutemusic/main/AppContainer.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.sosauce.cutemusic.main
-
-import androidx.media3.session.MediaController
-import com.google.common.util.concurrent.ListenableFuture
-
-interface AppContainer {
- val controllerFuture: ListenableFuture
-}
-
-class DefaultAppContainer(
- override val controllerFuture: ListenableFuture
-) : AppContainer
\ No newline at end of file
diff --git a/app/src/main/java/com/sosauce/cutemusic/main/MainActivity.kt b/app/src/main/java/com/sosauce/cutemusic/main/MainActivity.kt
index 6cfae01..65aacae 100644
--- a/app/src/main/java/com/sosauce/cutemusic/main/MainActivity.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/main/MainActivity.kt
@@ -39,7 +39,7 @@ class MainActivity : ComponentActivity() {
modifier = Modifier
.fillMaxSize()
) { _ ->
- Nav()
+ Nav()
}
}
}
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 80574eb..352ec28 100644
--- a/app/src/main/java/com/sosauce/cutemusic/main/PlaybackService.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/main/PlaybackService.kt
@@ -2,46 +2,88 @@ package com.sosauce.cutemusic.main
import android.app.PendingIntent
import android.content.Intent
-import androidx.compose.runtime.getValue
import androidx.media3.common.AudioAttributes
import androidx.media3.common.C
import androidx.media3.common.MediaMetadata
import androidx.media3.common.Player
+import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
+import androidx.media3.session.DefaultMediaNotificationProvider
import androidx.media3.session.MediaLibraryService
+import androidx.media3.session.MediaLibraryService.MediaLibrarySession
import androidx.media3.session.MediaSession
-import com.sosauce.cutemusic.data.datastore.rememberKillService
+import com.sosauce.cutemusic.R
-class PlaybackService : MediaLibraryService() {
+
+class PlaybackService : MediaLibraryService(),
+ MediaLibrarySession.Callback,
+ Player.Listener {
private var mediaLibrarySession: MediaLibrarySession? = null
- private var callback: MediaLibrarySession.Callback = object : MediaLibrarySession.Callback {}
private val audioAttributes = AudioAttributes
.Builder()
.setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
.setUsage(C.USAGE_MEDIA)
.build()
- private val listener = object : Player.Listener {
- override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) {
- super.onMediaMetadataChanged(mediaMetadata)
- sendMusicBroadcast(mediaMetadata.title.toString())
- }
- }
-
+ override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) {
+ super.onMediaMetadataChanged(mediaMetadata)
+ sendMusicBroadcast(mediaMetadata.title.toString())
+ }
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession? =
mediaLibrarySession
+ @UnstableApi
override fun onCreate() {
super.onCreate()
+
val player = ExoPlayer.Builder(applicationContext)
.setAudioAttributes(audioAttributes, true)
.setHandleAudioBecomingNoisy(true)
.build()
mediaLibrarySession = MediaLibrarySession
- .Builder(this, player, callback)
+ .Builder(this, player, this)
+ .setShowPlayButtonIfPlaybackIsSuppressed(false)
+
+// .setBitmapLoader(object : BitmapLoader {
+//
+// override fun supportsMimeType(mimeType: String): Boolean = true
+//
+// override fun decodeBitmap(data: ByteArray): ListenableFuture =
+// throw UnsupportedOperationException()
+//
+// override fun loadBitmap(uri: Uri): ListenableFuture = throw UnsupportedOperationException()
+//
+// override fun loadBitmapFromMetadata(metadata: MediaMetadata): ListenableFuture? {
+// val completer = SettableFuture.create()
+// val request = ImageRequest.Builder(this@PlaybackService)
+// .data(
+// if (metadata.artworkUri == Uri.parse("content://media/external/audio/media/1000000397/albumart")) {
+// R.drawable.artist
+// } else {
+// metadata.artworkUri
+// }
+// )
+// .target(
+// onSuccess = { result ->
+// completer.set((result as BitmapImage).bitmap)
+// },
+// onError = { _ ->
+// completer.setException(Exception("Error"))
+// }
+// )
+// .build()
+// println("Art URI: ${metadata.artworkUri}")
+// ImageLoader(this@PlaybackService).enqueue(request)
+//
+// return completer
+// }
+//
+// }
+
+//)
.setSessionActivity(
PendingIntent.getActivity(
this,
@@ -51,8 +93,13 @@ class PlaybackService : MediaLibraryService() {
)
)
.build()
+ setMediaNotificationProvider(
+ DefaultMediaNotificationProvider.Builder(this).build().apply {
+ setSmallIcon(R.drawable.round_music_note_24)
+ }
+ )
- player.addListener(listener)
+ player.addListener(this)
}
@@ -73,6 +120,7 @@ class PlaybackService : MediaLibraryService() {
stopSelf()
}
+
companion object {
private const val CURRENTLY_PLAYING_CHANGED = "CM_CUR_PLAY_CHANGED"
}
@@ -80,10 +128,9 @@ class PlaybackService : MediaLibraryService() {
private fun sendMusicBroadcast(
currentlyPlaying: String
) {
- val intent = Intent(CURRENTLY_PLAYING_CHANGED).apply {
+ Intent(CURRENTLY_PLAYING_CHANGED).apply {
putExtra("currentlyPlaying", currentlyPlaying)
+ sendBroadcast(this)
}
- sendBroadcast(intent)
}
-
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/sosauce/cutemusic/main/quickplay/QuickPlayActivity.kt b/app/src/main/java/com/sosauce/cutemusic/main/quickplay/QuickPlayActivity.kt
index 1444998..333ace8 100644
--- a/app/src/main/java/com/sosauce/cutemusic/main/quickplay/QuickPlayActivity.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/main/quickplay/QuickPlayActivity.kt
@@ -33,6 +33,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.sosauce.cutemusic.data.actions.PlayerActions
import com.sosauce.cutemusic.ui.screens.playing.components.MusicSlider
import com.sosauce.cutemusic.ui.shared_components.CuteText
@@ -56,7 +57,8 @@ class QuickPlayActivity : ComponentActivity() {
) { _ ->
MaterialTheme {
var uri by remember { mutableStateOf(null) }
- val vm = koinViewModel()
+ val viewModel = koinViewModel()
+ val musicState by viewModel.musicState.collectAsStateWithLifecycle()
when {
intent?.action == Intent.ACTION_SEND -> {
@@ -82,7 +84,7 @@ class QuickPlayActivity : ComponentActivity() {
horizontalAlignment = Alignment.CenterHorizontally
) {
CuteText(
- text = vm.currentlyPlaying,
+ text = musicState.currentlyPlaying,
color = MaterialTheme.colorScheme.onBackground,
fontSize = 20.sp,
modifier = Modifier.basicMarquee()
@@ -91,13 +93,16 @@ class QuickPlayActivity : ComponentActivity() {
)
Spacer(modifier = Modifier.height(5.dp))
CuteText(
- text = vm.currentArtist,
+ text = musicState.currentArtist,
color = MaterialTheme.colorScheme.onBackground.copy(0.85f),
fontSize = 14.sp,
modifier = Modifier.basicMarquee()
)
- MusicSlider(vm)
+ MusicSlider(
+ viewModel = viewModel,
+ musicState = musicState
+ )
Spacer(modifier = Modifier.height(7.dp))
Row(
modifier = Modifier.fillMaxWidth(),
@@ -106,13 +111,13 @@ class QuickPlayActivity : ComponentActivity() {
) {
FloatingActionButton(
onClick = {
- if (!vm.isPlaylistEmptyAndDataNotNull()) vm.quickPlay(uri) else vm.handlePlayerActions(
+ if (!viewModel.isPlayerReady()) viewModel.quickPlay(uri) else viewModel.handlePlayerActions(
PlayerActions.PlayOrPause
)
}
) {
Icon(
- imageVector = if (vm.isCurrentlyPlaying) Icons.Rounded.Pause else Icons.Rounded.PlayArrow,
+ imageVector = if (musicState.isCurrentlyPlaying) Icons.Rounded.Pause else Icons.Rounded.PlayArrow,
contentDescription = "pause/play button"
)
}
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/customs/CustomExtention.kt b/app/src/main/java/com/sosauce/cutemusic/ui/customs/CustomExtention.kt
deleted file mode 100644
index 751fc12..0000000
--- a/app/src/main/java/com/sosauce/cutemusic/ui/customs/CustomExtention.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.sosauce.cutemusic.ui.customs
-
-import android.content.Context
-import android.content.Intent
-import androidx.media3.common.Player
-import java.util.Locale
-
-fun Long.formatBinarySize(): String {
- val kiloByteAsByte = 1.0 * 1024.0
- val megaByteAsByte = 1.0 * 1024.0 * 1024.0
- val gigaByteAsByte = 1.0 * 1024.0 * 1024.0 * 1024.0
- return when {
- this < kiloByteAsByte -> "${this.toDouble()} B"
- this >= kiloByteAsByte && this < megaByteAsByte -> "${
- String.format(
- Locale.getDefault(),
- "%.2f",
- (this / kiloByteAsByte)
- )
- } KB"
-
- this >= megaByteAsByte && this < gigaByteAsByte -> "${
- String.format(
- Locale.getDefault(),
- "%.2f",
- (this / megaByteAsByte)
- )
- } MB"
-
- else -> "Too Big!"
- }
-}
-
-fun Context.restart() {
- val intent = packageManager.getLaunchIntentForPackage(packageName)!!
- val componentName = intent.component!!
- val restartIntent = Intent.makeRestartActivityTask(componentName)
- startActivity(restartIntent)
- Runtime.getRuntime().exit(0)
-}
-
-fun Player.playAtIndex(
- mediaId: String
-) {
- val index = (0 until mediaItemCount).indexOfFirst { getMediaItemAt(it).mediaId == mediaId }
- index.takeIf { it != -1 }?.let {
- seekTo(it, 0)
- play()
- }
-}
\ No newline at end of file
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 cbc9ba5..4a87145 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
@@ -2,20 +2,17 @@
package com.sosauce.cutemusic.ui.navigation
-import android.annotation.SuppressLint
-import android.content.Intent
-import android.util.Log
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionLayout
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
-import androidx.compose.ui.platform.LocalContext
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.toRoute
import com.sosauce.cutemusic.data.actions.MetadataActions
+import com.sosauce.cutemusic.data.actions.PlayerActions
import com.sosauce.cutemusic.data.datastore.rememberAllBlacklistedFolders
import com.sosauce.cutemusic.ui.screens.album.AlbumDetailsScreen
import com.sosauce.cutemusic.ui.screens.album.AlbumsScreen
@@ -32,6 +29,9 @@ import com.sosauce.cutemusic.ui.shared_components.PostViewModel
import com.sosauce.cutemusic.utils.ListToHandle
import org.koin.androidx.compose.koinViewModel
+// https://stackoverflow.com/a/78771053
+
+
@Composable
fun Nav() {
@@ -42,7 +42,7 @@ fun Nav() {
val blacklistedFolders by rememberAllBlacklistedFolders()
val musics = postViewModel.musics
.filter { it.mediaMetadata.extras?.getString("folder") !in blacklistedFolders }
-
+ val musicState by viewModel.musicState.collectAsStateWithLifecycle()
SharedTransitionLayout {
@@ -55,11 +55,9 @@ fun Nav() {
musics = musics,
selectedIndex = viewModel.selectedItem,
onNavigateTo = { navController.navigate(it) },
- currentlyPlaying = viewModel.currentlyPlaying,
- isCurrentlyPlaying = viewModel.isCurrentlyPlaying,
- onShortClick = {
- viewModel.itemClicked(it, musics)
- },
+ currentlyPlaying = musicState.currentlyPlaying,
+ isCurrentlyPlaying = musicState.isCurrentlyPlaying,
+ onShortClick = { viewModel.handlePlayerActions(PlayerActions.StartPlayback(it)) },
onNavigationItemClicked = { index, item ->
navController.navigate(item.navigateTo) {
viewModel.selectedItem = index
@@ -75,8 +73,8 @@ fun Nav() {
)
)
},
- isPlaylistEmpty = viewModel.isPlaylistEmptyAndDataNotNull(),
- currentMusicUri = viewModel.currentMusicUri,
+ isPlayerReady = viewModel.isPlayerReady(),
+ currentMusicUri = musicState.currentMusicUri,
onHandlePlayerAction = { viewModel.handlePlayerActions(it) },
onDeleteMusic = { uris, intentSender ->
postViewModel.deleteMusic(
@@ -95,7 +93,12 @@ fun Nav() {
listToHandle = ListToHandle.TRACKS,
query = query
)
- }
+ },
+ onChargeAlbumSongs = postViewModel::albumSongs,
+ onChargeArtistLists = {
+ postViewModel.artistSongs(it)
+ postViewModel.artistAlbums(it)
+ },
)
}
@@ -115,10 +118,10 @@ fun Nav() {
query = query
)
},
- currentlyPlaying = viewModel.currentlyPlaying,
+ currentlyPlaying = musicState.currentlyPlaying,
chargePVMAlbumSongs = postViewModel::albumSongs,
- isPlaylistEmpty = viewModel.isPlaylistEmptyAndDataNotNull(),
- isPlaying = viewModel.isCurrentlyPlaying,
+ isPlayerReady = viewModel.isPlayerReady(),
+ isPlaying = musicState.isCurrentlyPlaying,
onHandlePlayerActions = viewModel::handlePlayerActions,
onNavigate = { navController.navigate(it) },
onNavigationItemClicked = { index, item ->
@@ -142,15 +145,15 @@ fun Nav() {
}
},
selectedIndex = viewModel.selectedItem,
- chargePVMLists = {
+ onChargeArtistLists = {
postViewModel.artistSongs(it)
postViewModel.artistAlbums(it)
},
- currentlyPlaying = viewModel.currentlyPlaying,
+ currentlyPlaying = musicState.currentlyPlaying,
onHandlePlayerActions = viewModel::handlePlayerActions,
- isPlaying = viewModel.isCurrentlyPlaying,
+ isPlaying = musicState.isCurrentlyPlaying,
animatedVisibilityScope = this,
- isPlaylistEmpty = viewModel.isPlaylistEmptyAndDataNotNull(),
+ isPlayerReady = viewModel.isPlayerReady(),
onHandleSorting = { sortingType ->
postViewModel.handleFiltering(
listToHandle = ListToHandle.ARTISTS,
@@ -170,7 +173,13 @@ fun Nav() {
NowPlayingScreen(
navController = navController,
viewModel = viewModel,
- animatedVisibilityScope = this
+ animatedVisibilityScope = this,
+ musicState = musicState,
+ onChargeAlbumSongs = postViewModel::albumSongs,
+ onChargeArtistLists = {
+ postViewModel.artistSongs(it)
+ postViewModel.artistAlbums(it)
+ }
)
}
composable {
@@ -186,7 +195,10 @@ fun Nav() {
album = album,
viewModel = viewModel,
onPopBackStack = navController::navigateUp,
- postViewModel = postViewModel
+ postViewModel = postViewModel,
+ musicState = musicState,
+ animatedVisibilityScope = this,
+ onNavigate = { screen -> navController.navigate(screen) },
)
}
@@ -199,7 +211,9 @@ fun Nav() {
navController = navController,
viewModel = viewModel,
postViewModel = postViewModel,
- onNavigate = { screen -> navController.navigate(screen) }
+ onNavigate = { screen -> navController.navigate(screen) },
+ musicState = musicState,
+ animatedVisibilityScope = this
)
}
}
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/screens/album/AlbumDetailsLandscape.kt b/app/src/main/java/com/sosauce/cutemusic/ui/screens/album/AlbumDetailsLandscape.kt
index 94b87f2..71c1ccd 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/screens/album/AlbumDetailsLandscape.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/screens/album/AlbumDetailsLandscape.kt
@@ -29,6 +29,8 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil3.compose.AsyncImage
import com.sosauce.cutemusic.R
+import com.sosauce.cutemusic.data.MusicState
+import com.sosauce.cutemusic.data.actions.PlayerActions
import com.sosauce.cutemusic.domain.model.Album
import com.sosauce.cutemusic.ui.screens.main.MusicListItem
import com.sosauce.cutemusic.ui.shared_components.CuteText
@@ -41,7 +43,8 @@ fun AlbumDetailsLandscape(
album: Album,
onNavigateUp: () -> Unit,
postViewModel: PostViewModel,
- viewModel: MusicViewModel
+ viewModel: MusicViewModel,
+ musicState: MusicState
) {
val albumSongs by remember { mutableStateOf(postViewModel.albumSongs) }
@@ -100,8 +103,14 @@ fun AlbumDetailsLandscape(
items(albumSongs, key = { it.mediaId }) { music ->
MusicListItem(
music = music,
- currentMusicUri = viewModel.currentMusicUri,
- onShortClick = { viewModel.itemClicked(it, listOf()) }
+ currentMusicUri = musicState.currentMusicUri,
+ onShortClick = {
+ viewModel.handlePlayerActions(
+ PlayerActions.StartPlayback(
+ it
+ )
+ )
+ }
)
}
}
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 30b2756..0eb3f5c 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
@@ -1,7 +1,10 @@
-@file:OptIn(ExperimentalMaterial3Api::class)
+@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class)
package com.sosauce.cutemusic.ui.screens.album
+import androidx.compose.animation.AnimatedVisibilityScope
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.SharedTransitionScope
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -11,15 +14,17 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
+import androidx.compose.material.icons.rounded.Shuffle
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
@@ -41,20 +46,30 @@ import androidx.compose.ui.unit.sp
import androidx.media3.common.MediaItem
import coil3.compose.AsyncImage
import com.sosauce.cutemusic.R
+import com.sosauce.cutemusic.data.MusicState
+import com.sosauce.cutemusic.data.actions.PlayerActions
import com.sosauce.cutemusic.data.datastore.rememberIsLandscape
import com.sosauce.cutemusic.domain.model.Album
+import com.sosauce.cutemusic.ui.navigation.Screen
import com.sosauce.cutemusic.ui.screens.main.MusicListItem
+import com.sosauce.cutemusic.ui.shared_components.CuteSearchbar
import com.sosauce.cutemusic.ui.shared_components.CuteText
import com.sosauce.cutemusic.ui.shared_components.MusicViewModel
import com.sosauce.cutemusic.ui.shared_components.PostViewModel
import com.sosauce.cutemusic.utils.ImageUtils
+import com.sosauce.cutemusic.utils.rememberSearchbarAlignment
+import com.sosauce.cutemusic.utils.rememberSearchbarMaxFloatValue
+import com.sosauce.cutemusic.utils.rememberSearchbarRightPadding
@Composable
-fun AlbumDetailsScreen(
+fun SharedTransitionScope.AlbumDetailsScreen(
album: Album,
viewModel: MusicViewModel,
postViewModel: PostViewModel,
onPopBackStack: () -> Unit,
+ musicState: MusicState,
+ animatedVisibilityScope: AnimatedVisibilityScope,
+ onNavigate: (Screen) -> Unit,
) {
val albumSongs by remember { mutableStateOf(postViewModel.albumSongs) }
@@ -63,26 +78,33 @@ fun AlbumDetailsScreen(
album = album,
onNavigateUp = { onPopBackStack() },
postViewModel = postViewModel,
- viewModel = viewModel
+ viewModel = viewModel,
+ musicState = musicState
)
} else {
AlbumDetailsContent(
album = album,
viewModel = viewModel,
onPopBackStack = { onPopBackStack() },
- albumSongs = albumSongs
+ albumSongs = albumSongs,
+ musicState = musicState,
+ animatedVisibilityScope = animatedVisibilityScope,
+ onNavigate = onNavigate,
)
}
}
@Composable
-private fun AlbumDetailsContent(
+private fun SharedTransitionScope.AlbumDetailsContent(
album: Album,
viewModel: MusicViewModel,
onPopBackStack: () -> Unit,
- albumSongs: List
- ) {
+ albumSongs: List,
+ musicState: MusicState,
+ animatedVisibilityScope: AnimatedVisibilityScope,
+ onNavigate: (Screen) -> Unit,
+) {
val context = LocalContext.current
Scaffold(
topBar = {
@@ -97,6 +119,25 @@ private fun AlbumDetailsContent(
}
}
)
+ },
+ floatingActionButton = {
+ FloatingActionButton(
+ onClick = {
+ viewModel.handlePlayerActions(
+ PlayerActions.StartAlbumPlayback(
+ albumName = album.name,
+ mediaId = null
+ )
+ )
+ },
+ modifier = Modifier
+ .padding(bottom = 55.dp)
+ ) {
+ Icon(
+ imageVector = Icons.Rounded.Shuffle,
+ contentDescription = null
+ )
+ }
}
) { values ->
Box(
@@ -108,59 +149,83 @@ private fun AlbumDetailsContent(
top = values.calculateTopPadding(),
bottom = values.calculateBottomPadding()
)
- .verticalScroll(rememberScrollState())
) {
- Column {
- Row(
- horizontalArrangement = Arrangement.Start,
- verticalAlignment = Alignment.Top,
- modifier = Modifier.fillMaxWidth()
- ) {
- AsyncImage(
- model = ImageUtils.imageRequester(
- img = ImageUtils.getAlbumArt(album.id),
- context = context
- ),
- contentDescription = stringResource(R.string.artwork),
- modifier = Modifier
- .size(150.dp)
- .clip(RoundedCornerShape(12.dp)),
- contentScale = ContentScale.Crop
- )
- Spacer(Modifier.width(10.dp))
- Column(
- horizontalAlignment = Alignment.Start
+ LazyColumn {
+ item {
+ Row(
+ horizontalArrangement = Arrangement.Start,
+ verticalAlignment = Alignment.Top,
+ modifier = Modifier.fillMaxWidth()
) {
- CuteText(
- text = album.name,
- fontSize = 22.sp,
- modifier = Modifier.basicMarquee()
- )
- CuteText(
- text = album.artist,
- fontSize = 22.sp,
- color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.85f),
- modifier = Modifier.basicMarquee()
- )
- Spacer(Modifier.height(60.dp))
- CuteText(
- text = "${albumSongs.size} ${if (albumSongs.size <= 1) "song" else "songs"}",
- fontSize = 22.sp,
- modifier = Modifier.basicMarquee()
+ AsyncImage(
+ model = ImageUtils.imageRequester(
+ img = ImageUtils.getAlbumArt(album.id),
+ context = context
+ ),
+ contentDescription = stringResource(R.string.artwork),
+ modifier = Modifier
+ .size(150.dp)
+ .clip(RoundedCornerShape(12.dp)),
+ contentScale = ContentScale.Crop
)
+ Spacer(Modifier.width(10.dp))
+ Column(
+ horizontalAlignment = Alignment.Start
+ ) {
+ CuteText(
+ text = album.name,
+ fontSize = 22.sp,
+ modifier = Modifier.basicMarquee()
+ )
+ CuteText(
+ text = album.artist,
+ fontSize = 22.sp,
+ color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.85f),
+ modifier = Modifier.basicMarquee()
+ )
+ Spacer(Modifier.height(60.dp))
+ CuteText(
+ text = "${albumSongs.size} ${if (albumSongs.size <= 1) "song" else "songs"}",
+ fontSize = 22.sp,
+ modifier = Modifier.basicMarquee()
+ )
+ }
}
- }
- Spacer(Modifier.height(10.dp))
- Column {
- albumSongs.forEach { music ->
- MusicListItem(
- music = music,
- onShortClick = { viewModel.itemClicked(it, listOf()) },
- currentMusicUri = viewModel.currentMusicUri
- )
+ Spacer(Modifier.height(10.dp))
+ Column {
+ albumSongs.forEach { music ->
+ MusicListItem(
+ music = music,
+ onShortClick = {
+ viewModel.handlePlayerActions(
+ PlayerActions.StartAlbumPlayback(
+ albumName = music.mediaMetadata.albumTitle.toString(),
+ mediaId = it
+ )
+ )
+ },
+ currentMusicUri = musicState.currentMusicUri
+ )
+ }
}
}
}
+
+ CuteSearchbar(
+ currentlyPlaying = musicState.currentlyPlaying,
+ isPlayerReady = viewModel.isPlayerReady(),
+ isPlaying = musicState.isCurrentlyPlaying,
+ onHandlePlayerActions = viewModel::handlePlayerActions,
+ animatedVisibilityScope = animatedVisibilityScope,
+ modifier = Modifier
+ .navigationBarsPadding()
+ .fillMaxWidth(rememberSearchbarMaxFloatValue())
+ .padding(end = rememberSearchbarRightPadding())
+ .align(rememberSearchbarAlignment()),
+ showSearchField = false,
+ onNavigate = { onNavigate(Screen.NowPlaying) }
+ )
+
}
}
}
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 29deafd..9f9f868 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
@@ -6,7 +6,6 @@ import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionScope
import androidx.compose.animation.core.animateFloatAsState
-import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.clickable
@@ -39,7 +38,6 @@ 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.draw.clip
import androidx.compose.ui.draw.rotate
@@ -48,7 +46,6 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
-import androidx.navigation.NavController
import coil3.compose.AsyncImage
import com.sosauce.cutemusic.R
import com.sosauce.cutemusic.data.actions.PlayerActions
@@ -57,9 +54,7 @@ import com.sosauce.cutemusic.domain.model.Album
import com.sosauce.cutemusic.ui.navigation.Screen
import com.sosauce.cutemusic.ui.shared_components.CuteSearchbar
import com.sosauce.cutemusic.ui.shared_components.CuteText
-import com.sosauce.cutemusic.ui.shared_components.MusicViewModel
import com.sosauce.cutemusic.ui.shared_components.NavigationItem
-import com.sosauce.cutemusic.ui.shared_components.PostViewModel
import com.sosauce.cutemusic.ui.shared_components.ScreenSelection
import com.sosauce.cutemusic.utils.ImageUtils
import com.sosauce.cutemusic.utils.SortingType
@@ -80,7 +75,7 @@ fun SharedTransitionScope.AlbumsScreen(
selectedIndex: Int,
isPlaying: Boolean,
onHandlePlayerActions: (PlayerActions) -> Unit,
- isPlaylistEmpty: Boolean,
+ isPlayerReady: Boolean,
onNavigationItemClicked: (Int, NavigationItem) -> Unit
) {
val isLandscape = rememberIsLandscape()
@@ -130,10 +125,12 @@ fun SharedTransitionScope.AlbumsScreen(
chargePVMAlbumSongs(album.name)
onNavigate(Screen.AlbumsDetails(album.id))
}
-// .thenIf(
-// index == 0 || index == 1 || index == 2 || index == 3, // booo bad
-// Modifier.statusBarsPadding()
-// )
+ .thenIf(
+ if (isLandscape)
+ index == 0 || index == 1 || index == 2 || index == 3
+ else index == 0 || index == 1,
+ Modifier.statusBarsPadding()
+ )
)
}
}
@@ -151,14 +148,7 @@ fun SharedTransitionScope.AlbumsScreen(
bottom = 5.dp,
end = rememberSearchbarRightPadding()
)
- .align(rememberSearchbarAlignment())
- .sharedElement(
- state = rememberSharedContentState(key = "searchbar"),
- animatedVisibilityScope = animatedVisibilityScope,
- boundsTransform = { _, _ ->
- tween(durationMillis = 500)
- }
- ),
+ .align(rememberSearchbarAlignment()),
placeholder = {
CuteText(
text = stringResource(id = R.string.search) + " " + stringResource(R.string.albums),
@@ -179,7 +169,8 @@ fun SharedTransitionScope.AlbumsScreen(
onDismissRequest = { screenSelectionExpanded = false },
modifier = Modifier
.width(180.dp)
- .background(color = MaterialTheme.colorScheme.surface)
+ .background(color = MaterialTheme.colorScheme.surface),
+ shape = RoundedCornerShape(24.dp)
) {
ScreenSelection(
onNavigationItemClicked = onNavigationItemClicked,
@@ -223,7 +214,7 @@ fun SharedTransitionScope.AlbumsScreen(
onHandlePlayerActions = onHandlePlayerActions,
isPlaying = isPlaying,
animatedVisibilityScope = animatedVisibilityScope,
- isPlaylistEmpty = isPlaylistEmpty,
+ isPlayerReady = isPlayerReady,
onNavigate = { onNavigate(Screen.NowPlaying) }
)
}
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 3218ff5..b1d2783 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
@@ -1,7 +1,10 @@
-@file:OptIn(ExperimentalMaterial3Api::class)
+@file:OptIn(ExperimentalMaterial3Api::class, 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.Box
import androidx.compose.foundation.layout.Column
@@ -10,6 +13,7 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
@@ -17,7 +21,9 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material.icons.rounded.Shuffle
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@@ -29,25 +35,34 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
+import com.sosauce.cutemusic.data.MusicState
+import com.sosauce.cutemusic.data.actions.PlayerActions
import com.sosauce.cutemusic.data.datastore.rememberIsLandscape
import com.sosauce.cutemusic.domain.model.Artist
import com.sosauce.cutemusic.ui.navigation.Screen
import com.sosauce.cutemusic.ui.screens.album.AlbumCard
import com.sosauce.cutemusic.ui.screens.main.MusicListItem
+import com.sosauce.cutemusic.ui.shared_components.CuteSearchbar
import com.sosauce.cutemusic.ui.shared_components.CuteText
import com.sosauce.cutemusic.ui.shared_components.MusicViewModel
import com.sosauce.cutemusic.ui.shared_components.PostViewModel
+import com.sosauce.cutemusic.utils.rememberSearchbarAlignment
+import com.sosauce.cutemusic.utils.rememberSearchbarMaxFloatValue
+import com.sosauce.cutemusic.utils.rememberSearchbarRightPadding
@Composable
-fun ArtistDetails(
+fun SharedTransitionScope.ArtistDetails(
artist: Artist,
navController: NavController,
viewModel: MusicViewModel,
postViewModel: PostViewModel,
onNavigate: (Screen) -> Unit,
+ musicState: MusicState,
+ animatedVisibilityScope: AnimatedVisibilityScope
) {
val artistSongs by remember { mutableStateOf(postViewModel.artistSongs) }
@@ -58,11 +73,11 @@ fun ArtistDetails(
onNavigateUp = navController::navigateUp,
artistAlbums = artistAlbums,
artistSongs = artistSongs,
- onClickPlay = { viewModel.itemClicked(it, listOf()) },
+ onClickPlay = { viewModel.handlePlayerActions(PlayerActions.StartPlayback(it)) },
onNavigate = { navController.navigate(it) },
chargePVMAlbumSongs = { postViewModel.albumSongs(it) },
artist = artist,
- currentMusicUri = viewModel.currentMusicUri
+ currentMusicUri = musicState.currentMusicUri
)
} else {
Scaffold(
@@ -94,12 +109,36 @@ fun ArtistDetails(
}
}
)
+ },
+ floatingActionButton = {
+ FloatingActionButton(
+ onClick = {
+ viewModel.handlePlayerActions(
+ PlayerActions.StartArtistPlayback(
+ artistName = artist.name,
+ mediaId = null
+ )
+ )
+ },
+ modifier = Modifier
+ .padding(bottom = 55.dp)
+ ) {
+ Icon(
+ imageVector = Icons.Rounded.Shuffle,
+ contentDescription = null
+ )
+ }
}
) { values ->
Box(
modifier = Modifier
.fillMaxSize()
- .padding(values)
+ .padding(
+ start = values.calculateLeftPadding(LayoutDirection.Ltr) + 10.dp,
+ end = values.calculateRightPadding(LayoutDirection.Rtl) + 10.dp,
+ top = values.calculateTopPadding(),
+ bottom = values.calculateBottomPadding()
+ )
) {
Column {
LazyRow {
@@ -122,13 +161,33 @@ fun ArtistDetails(
items(artistSongs) { music ->
MusicListItem(
music = music,
- onShortClick = { viewModel.itemClicked(it, listOf()) },
- currentMusicUri = viewModel.currentMusicUri
+ onShortClick = {
+ viewModel.handlePlayerActions(
+ PlayerActions.StartArtistPlayback(
+ artistName = artist.name,
+ mediaId = it
+ )
+ )
+ },
+ currentMusicUri = musicState.currentMusicUri
)
}
}
-
}
+ CuteSearchbar(
+ currentlyPlaying = musicState.currentlyPlaying,
+ isPlayerReady = viewModel.isPlayerReady(),
+ isPlaying = musicState.isCurrentlyPlaying,
+ onHandlePlayerActions = viewModel::handlePlayerActions,
+ animatedVisibilityScope = animatedVisibilityScope,
+ modifier = Modifier
+ .navigationBarsPadding()
+ .fillMaxWidth(rememberSearchbarMaxFloatValue())
+ .padding(end = rememberSearchbarRightPadding())
+ .align(rememberSearchbarAlignment()),
+ showSearchField = false,
+ onNavigate = { onNavigate(Screen.NowPlaying) }
+ )
}
}
}
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/screens/artist/ArtistsScreen.kt b/app/src/main/java/com/sosauce/cutemusic/ui/screens/artist/ArtistsScreen.kt
index a70f590..e4dfdc1 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/screens/artist/ArtistsScreen.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/screens/artist/ArtistsScreen.kt
@@ -6,7 +6,6 @@ import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionScope
import androidx.compose.animation.core.animateFloatAsState
-import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.clickable
@@ -21,6 +20,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowUpward
import androidx.compose.material.icons.rounded.Person
@@ -43,18 +43,14 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
-import androidx.navigation.NavController
import coil3.compose.AsyncImage
import com.sosauce.cutemusic.R
import com.sosauce.cutemusic.data.actions.PlayerActions
-import com.sosauce.cutemusic.data.datastore.rememberIsLandscape
import com.sosauce.cutemusic.domain.model.Artist
import com.sosauce.cutemusic.ui.navigation.Screen
import com.sosauce.cutemusic.ui.shared_components.CuteSearchbar
import com.sosauce.cutemusic.ui.shared_components.CuteText
-import com.sosauce.cutemusic.ui.shared_components.MusicViewModel
import com.sosauce.cutemusic.ui.shared_components.NavigationItem
-import com.sosauce.cutemusic.ui.shared_components.PostViewModel
import com.sosauce.cutemusic.ui.shared_components.ScreenSelection
import com.sosauce.cutemusic.utils.ImageUtils
import com.sosauce.cutemusic.utils.SortingType
@@ -69,12 +65,12 @@ fun SharedTransitionScope.ArtistsScreen(
onHandleSorting: (SortingType) -> Unit,
onHandleSearching: (String) -> Unit,
currentlyPlaying: String,
- chargePVMLists: (String) -> Unit,
+ onChargeArtistLists: (String) -> Unit,
onNavigate: (Screen) -> Unit,
selectedIndex: Int,
isPlaying: Boolean,
onHandlePlayerActions: (PlayerActions) -> Unit,
- isPlaylistEmpty: Boolean,
+ isPlayerReady: Boolean,
onNavigationItemClicked: (Int, NavigationItem) -> Unit
) {
@@ -122,7 +118,7 @@ fun SharedTransitionScope.ArtistsScreen(
)
) {
ArtistInfoList(it) {
- chargePVMLists(it.name)
+ onChargeArtistLists(it.name)
onNavigate(Screen.ArtistsDetails(it.id))
}
}
@@ -142,14 +138,7 @@ fun SharedTransitionScope.ArtistsScreen(
bottom = 5.dp,
end = rememberSearchbarRightPadding()
)
- .align(rememberSearchbarAlignment())
- .sharedElement(
- state = rememberSharedContentState(key = "searchbar"),
- animatedVisibilityScope = animatedVisibilityScope,
- boundsTransform = { _, _ ->
- tween(durationMillis = 500)
- }
- ),
+ .align(rememberSearchbarAlignment()),
placeholder = {
CuteText(
text = stringResource(id = R.string.search) + " " + stringResource(R.string.artists),
@@ -169,7 +158,8 @@ fun SharedTransitionScope.ArtistsScreen(
onDismissRequest = { screenSelectionExpanded = false },
modifier = Modifier
.width(180.dp)
- .background(color = MaterialTheme.colorScheme.surface)
+ .background(color = MaterialTheme.colorScheme.surface),
+ shape = RoundedCornerShape(24.dp)
) {
ScreenSelection(
onNavigationItemClicked = onNavigationItemClicked,
@@ -182,9 +172,14 @@ fun SharedTransitionScope.ArtistsScreen(
IconButton(
onClick = {
isSortedByASC = !isSortedByASC
- when(isSortedByASC) {
- true -> { onHandleSorting(SortingType.ASCENDING) }
- false -> { onHandleSorting(SortingType.DESCENDING) }
+ when (isSortedByASC) {
+ true -> {
+ onHandleSorting(SortingType.ASCENDING)
+ }
+
+ false -> {
+ onHandleSorting(SortingType.DESCENDING)
+ }
}
}
) {
@@ -208,7 +203,7 @@ fun SharedTransitionScope.ArtistsScreen(
onHandlePlayerActions = onHandlePlayerActions,
isPlaying = isPlaying,
animatedVisibilityScope = animatedVisibilityScope,
- isPlaylistEmpty = isPlaylistEmpty,
+ isPlayerReady = isPlayerReady,
onNavigate = { onNavigate(Screen.NowPlaying) }
)
}
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/screens/lyrics/LyricsView.kt b/app/src/main/java/com/sosauce/cutemusic/ui/screens/lyrics/LyricsView.kt
index 254afbd..74c9df7 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/screens/lyrics/LyricsView.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/screens/lyrics/LyricsView.kt
@@ -2,7 +2,6 @@
package com.sosauce.cutemusic.ui.screens.lyrics
-import android.util.Log
import android.view.WindowManager
import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.ExperimentalFoundationApi
@@ -40,30 +39,28 @@ import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import androidx.lifecycle.viewmodel.compose.viewModel
+import com.sosauce.cutemusic.data.MusicState
import com.sosauce.cutemusic.data.actions.PlayerActions
import com.sosauce.cutemusic.domain.model.Lyrics
import com.sosauce.cutemusic.main.MainActivity
import com.sosauce.cutemusic.ui.shared_components.CuteText
import com.sosauce.cutemusic.ui.shared_components.MusicViewModel
-import org.koin.androidx.compose.koinViewModel
@Composable
fun LyricsView(
viewModel: MusicViewModel,
onHideLyrics: () -> Unit,
- path: String,
- isLandscape: Boolean = false
+ isLandscape: Boolean = false,
+ musicState: MusicState
) {
var currentLyric by remember { mutableStateOf(Lyrics()) }
val clipboardManager = LocalClipboardManager.current
- val indexToScrollTo = viewModel.currentLyrics.indexOfFirst { lyric ->
- viewModel.currentPosition in lyric.timestamp until (viewModel.currentLyrics.getOrNull(
- viewModel.currentLyrics.indexOf(lyric) + 1
+ val indexToScrollTo = musicState.currentLyrics.indexOfFirst { lyric ->
+ musicState.currentPosition in lyric.timestamp until (musicState.currentLyrics.getOrNull(
+ musicState.currentLyrics.indexOf(lyric) + 1
)?.timestamp ?: 0)
}
val lazyListState = rememberLazyListState(
@@ -94,29 +91,29 @@ fun LyricsView(
state = lazyListState
) {
- if (viewModel.currentLyrics.isEmpty()) {
+ if (musicState.currentLyrics.isEmpty()) {
item {
CuteText(
- text = viewModel.loadEmbeddedLyrics(path),
+ text = viewModel.loadEmbeddedLyrics(musicState.currentPath),
)
}
} else {
itemsIndexed(
- items = viewModel.currentLyrics,
+ items = musicState.currentLyrics,
key = { _, item -> item.timestamp }
) { index, lyric ->
- val nextTimestamp = remember(index, viewModel.currentLyrics) {
- if (index < viewModel.currentLyrics.size - 1) {
- viewModel.currentLyrics[index + 1].timestamp
+ val nextTimestamp = remember(index, musicState.currentLyrics) {
+ if (index < musicState.currentLyrics.size - 1) {
+ musicState.currentLyrics[index + 1].timestamp
} else {
0
}
}
- val isCurrentLyric = remember(viewModel.currentPosition, nextTimestamp) {
- viewModel.currentPosition in lyric.timestamp until nextTimestamp
+ val isCurrentLyric = remember(musicState.currentPosition, nextTimestamp) {
+ musicState.currentPosition in lyric.timestamp until nextTimestamp
}
val color by animateColorAsState(
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 7f79fc7..123cbf6 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
@@ -5,9 +5,14 @@
package com.sosauce.cutemusic.ui.screens.main
+import android.app.Activity
import android.net.Uri
+import android.widget.Toast
+import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.IntentSenderRequest
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.Crossfade
import androidx.compose.animation.ExperimentalSharedTransitionApi
@@ -19,6 +24,7 @@ import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
+import androidx.compose.animation.scaleOut
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.basicMarquee
@@ -27,7 +33,6 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
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.navigationBarsPadding
@@ -40,18 +45,25 @@ import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.rounded.Album
import androidx.compose.material.icons.rounded.ArrowUpward
+import androidx.compose.material.icons.rounded.Delete
+import androidx.compose.material.icons.rounded.Edit
+import androidx.compose.material.icons.rounded.Info
import androidx.compose.material.icons.rounded.MoreVert
import androidx.compose.material.icons.rounded.MusicNote
+import androidx.compose.material.icons.rounded.Person
import androidx.compose.material.icons.rounded.Settings
+import androidx.compose.material.icons.rounded.Shuffle
import androidx.compose.material3.DropdownMenu
+import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.ModalBottomSheet
-import androidx.compose.material3.rememberModalBottomSheetState
+import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -61,6 +73,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate
+import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
@@ -72,9 +85,9 @@ import com.sosauce.cutemusic.R
import com.sosauce.cutemusic.data.actions.PlayerActions
import com.sosauce.cutemusic.data.datastore.rememberHasSeenTip
import com.sosauce.cutemusic.ui.navigation.Screen
-import com.sosauce.cutemusic.ui.screens.main.components.BottomSheetContent
import com.sosauce.cutemusic.ui.shared_components.CuteSearchbar
import com.sosauce.cutemusic.ui.shared_components.CuteText
+import com.sosauce.cutemusic.ui.shared_components.MusicDetailsDialog
import com.sosauce.cutemusic.ui.shared_components.NavigationItem
import com.sosauce.cutemusic.ui.shared_components.ScreenSelection
import com.sosauce.cutemusic.utils.ImageUtils
@@ -94,13 +107,15 @@ fun SharedTransitionScope.MainScreen(
onNavigationItemClicked: (Int, NavigationItem) -> Unit,
selectedIndex: Int,
animatedVisibilityScope: AnimatedVisibilityScope,
- onLoadMetadata: ((String) -> Unit)? = null,
- isPlaylistEmpty: Boolean,
+ onLoadMetadata: (String) -> Unit = {},
+ isPlayerReady: Boolean,
currentMusicUri: String,
onHandlePlayerAction: (PlayerActions) -> Unit,
onDeleteMusic: (List, ActivityResultLauncher) -> Unit,
onHandleSorting: (SortingType) -> Unit,
- onHandleSearching: (String) -> Unit
+ onHandleSearching: (String) -> Unit,
+ onChargeAlbumSongs: (String) -> Unit,
+ onChargeArtistLists: (String) -> Unit
) {
var query by remember { mutableStateOf("") }
val state = rememberLazyListState()
@@ -112,166 +127,192 @@ fun SharedTransitionScope.MainScreen(
)
-
- Box(Modifier.fillMaxSize()) {
- LazyColumn(
- state = state
- ) {
- if (musics.isEmpty()) {
- item {
- CuteText(
- text = stringResource(id = R.string.no_musics_found),
- modifier = Modifier
- .padding(16.dp)
- .fillMaxWidth(),
- textAlign = TextAlign.Center
- )
- }
- } else {
- itemsIndexed(
- items = musics,
- key = { _, music -> music.mediaId }
- ) { index, music ->
- Column(
- modifier = Modifier
- .animateItem()
- .padding(
- vertical = 2.dp,
- horizontal = 4.dp
- )
- ) {
- MusicListItem(
- onShortClick = { onShortClick(music.mediaId) },
- music = music,
- onNavigate = { onNavigateTo(it) },
- currentMusicUri = currentMusicUri,
- onLoadMetadata = onLoadMetadata,
- showBottomSheet = true,
- modifier = Modifier.thenIf(
- index == 0,
- Modifier.statusBarsPadding()
- ),
- onDeleteMusic = onDeleteMusic
- )
- }
+ Scaffold(
+ floatingActionButton = {
+ AnimatedVisibility(
+ visible = !isPlayerReady,
+ exit = scaleOut(
+ // 2 times faster than the searchbar so it doesn't look too weird
+ animationSpec = tween(250),
+ transformOrigin = TransformOrigin(0.5f, 0.25f)
+ )
+ ) {
+ FloatingActionButton(
+ onClick = { onHandlePlayerAction(PlayerActions.PlayRandom) },
+ modifier = Modifier
+ .padding(bottom = 70.dp)
+ ) {
+ Icon(
+ imageVector = Icons.Rounded.Shuffle,
+ contentDescription = null
+ )
}
}
}
-
-
- Crossfade(
- targetState = state.canScrollForward || musics.size <= 15,
- label = "",
- modifier = Modifier.align(rememberSearchbarAlignment())
- ) { visible ->
- if (visible) {
- val transition = rememberInfiniteTransition(label = "Infinite Color Change")
- val color by transition.animateColor(
- initialValue = LocalContentColor.current,
- targetValue = MaterialTheme.colorScheme.errorContainer,
- animationSpec = infiniteRepeatable(
- tween(500),
- repeatMode = RepeatMode.Reverse
- ),
- label = ""
- )
- var hasSeenTip by rememberHasSeenTip()
-
- CuteSearchbar(
- query = query,
- onQueryChange = {
- query = it
- onHandleSearching(query)
- },
- modifier = Modifier
- .navigationBarsPadding()
- .fillMaxWidth(rememberSearchbarMaxFloatValue())
- .padding(
- bottom = 5.dp,
- end = rememberSearchbarRightPadding()
- )
- .sharedElement(
- state = rememberSharedContentState(key = "searchbar"),
- animatedVisibilityScope = animatedVisibilityScope,
- boundsTransform = { _, _ ->
- tween(durationMillis = 500)
- }
- ),
- placeholder = {
+ ) { _ ->
+ Box(Modifier.fillMaxSize()) {
+ LazyColumn(
+ state = state
+ ) {
+ if (musics.isEmpty()) {
+ item {
CuteText(
- text = stringResource(id = R.string.search) + " " + stringResource(id = R.string.music),
- color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.5f),
-
- )
- },
- leadingIcon = {
- IconButton(
- onClick = {
- screenSelectionExpanded = true
- // Let's prevent writing to datastore everytime the user clicks ;)
- if (!hasSeenTip) {
- hasSeenTip = true
- }
- }
+ text = stringResource(id = R.string.no_musics_found),
+ modifier = Modifier
+ .padding(16.dp)
+ .fillMaxWidth(),
+ textAlign = TextAlign.Center
+ )
+ }
+ } else {
+ itemsIndexed(
+ items = musics,
+ key = { _, music -> music.mediaId }
+ ) { index, music ->
+ Column(
+ modifier = Modifier
+ .animateItem()
+ .padding(
+ vertical = 2.dp,
+ horizontal = 4.dp
+ )
) {
- Icon(
- imageVector = Icons.Rounded.MusicNote,
- contentDescription = null,
- tint = if (!hasSeenTip) color else LocalContentColor.current
+ MusicListItem(
+ onShortClick = { onShortClick(music.mediaId) },
+ music = music,
+ onNavigate = { onNavigateTo(it) },
+ currentMusicUri = currentMusicUri,
+ onLoadMetadata = onLoadMetadata,
+ showBottomSheet = true,
+ modifier = Modifier.thenIf(
+ index == 0,
+ Modifier.statusBarsPadding()
+ ),
+ onDeleteMusic = onDeleteMusic,
+ onChargeAlbumSongs = onChargeAlbumSongs,
+ onChargeArtistLists = onChargeArtistLists
)
}
+ }
+ }
+ }
+ // TODO : How do you make it NOT scroll to the first item when sorting changes !!!!!
+ Crossfade(
+ targetState = true,
+ //targetState = state.canScrollForward || musics.size <= 15,
+ label = "",
+ modifier = Modifier.align(rememberSearchbarAlignment())
+ ) { visible ->
+ if (visible) {
+ val transition = rememberInfiniteTransition(label = "Infinite Color Change")
+ val color by transition.animateColor(
+ initialValue = LocalContentColor.current,
+ targetValue = MaterialTheme.colorScheme.errorContainer,
+ animationSpec = infiniteRepeatable(
+ tween(500),
+ repeatMode = RepeatMode.Reverse
+ ),
+ label = ""
+ )
+ var hasSeenTip by rememberHasSeenTip()
- DropdownMenu(
- expanded = screenSelectionExpanded,
- onDismissRequest = { screenSelectionExpanded = false },
- modifier = Modifier
- .width(180.dp)
- .background(color = MaterialTheme.colorScheme.surface)
- ) {
- ScreenSelection(
- onNavigationItemClicked = onNavigationItemClicked,
- selectedIndex = selectedIndex
- )
- }
- },
- trailingIcon = {
- Row {
+ CuteSearchbar(
+ query = query,
+ onQueryChange = {
+ query = it
+ onHandleSearching(query)
+ },
+ modifier = Modifier
+ .navigationBarsPadding()
+ .fillMaxWidth(rememberSearchbarMaxFloatValue())
+ .padding(
+ bottom = 5.dp,
+ end = rememberSearchbarRightPadding()
+ ),
+ placeholder = {
+ CuteText(
+ text = stringResource(id = R.string.search) + " " + stringResource(
+ id = R.string.music
+ ),
+ color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.5f),
+
+ )
+ },
+ leadingIcon = {
IconButton(
onClick = {
- isSortedByASC = !isSortedByASC
- when(isSortedByASC) {
- true -> { onHandleSorting(SortingType.ASCENDING) }
- false -> { onHandleSorting(SortingType.DESCENDING) }
+ screenSelectionExpanded = true
+ // Let's prevent writing to datastore everytime the user clicks ;)
+ if (!hasSeenTip) {
+ hasSeenTip = true
}
}
) {
Icon(
- imageVector = Icons.Rounded.ArrowUpward,
+ imageVector = Icons.Rounded.MusicNote,
contentDescription = null,
- modifier = Modifier.rotate(float)
+ tint = if (!hasSeenTip) color else LocalContentColor.current
)
}
- IconButton(
- onClick = { onNavigateTo(Screen.Settings) }
+
+
+ DropdownMenu(
+ expanded = screenSelectionExpanded,
+ onDismissRequest = { screenSelectionExpanded = false },
+ modifier = Modifier
+ .width(180.dp)
+ .background(color = MaterialTheme.colorScheme.surface),
+ shape = RoundedCornerShape(24.dp)
) {
- Icon(
- imageVector = Icons.Rounded.Settings,
- contentDescription = null
+ ScreenSelection(
+ onNavigationItemClicked = onNavigationItemClicked,
+ selectedIndex = selectedIndex
)
}
- }
- },
- currentlyPlaying = currentlyPlaying,
- onHandlePlayerActions = onHandlePlayerAction,
- isPlaying = isCurrentlyPlaying,
- animatedVisibilityScope = animatedVisibilityScope,
- isPlaylistEmpty = isPlaylistEmpty,
- onNavigate = { onNavigateTo(Screen.NowPlaying) }
- )
+ },
+ trailingIcon = {
+ Row {
+ IconButton(
+ onClick = {
+ isSortedByASC = !isSortedByASC
+ when (isSortedByASC) {
+ true -> {
+ onHandleSorting(SortingType.ASCENDING)
+ }
+
+ false -> {
+ onHandleSorting(SortingType.DESCENDING)
+ }
+ }
+ }
+ ) {
+ Icon(
+ imageVector = Icons.Rounded.ArrowUpward,
+ contentDescription = null,
+ modifier = Modifier.rotate(float)
+ )
+ }
+ IconButton(
+ onClick = { onNavigateTo(Screen.Settings) }
+ ) {
+ Icon(
+ imageVector = Icons.Rounded.Settings,
+ contentDescription = null
+ )
+ }
+ }
+ },
+ currentlyPlaying = currentlyPlaying,
+ onHandlePlayerActions = onHandlePlayerAction,
+ isPlaying = isCurrentlyPlaying,
+ animatedVisibilityScope = animatedVisibilityScope,
+ isPlayerReady = isPlayerReady,
+ onNavigate = { onNavigateTo(Screen.NowPlaying) }
+ )
+ }
}
}
-
}
}
@@ -279,17 +320,21 @@ fun SharedTransitionScope.MainScreen(
fun MusicListItem(
modifier: Modifier = Modifier,
music: MediaItem,
- onShortClick: (String) -> Unit,
+ onShortClick: (albumName: String) -> Unit,
onNavigate: (Screen) -> Unit = {},
currentMusicUri: String,
- onLoadMetadata: ((String) -> Unit)? = null,
+ onLoadMetadata: (String) -> Unit = {},
showBottomSheet: Boolean = false,
- onDeleteMusic: ((List, ActivityResultLauncher) -> Unit)? = null
+ onDeleteMusic: (List, ActivityResultLauncher) -> Unit = { _, _ -> },
+ onChargeAlbumSongs: (String) -> Unit = {},
+ onChargeArtistLists: (String) -> Unit = {},
) {
- val sheetState = rememberModalBottomSheetState()
val context = LocalContext.current
- var isSheetOpen by remember { mutableStateOf(false) }
+ var isDropDownExpanded by remember { mutableStateOf(false) }
+ var showDetailsDialog by remember { mutableStateOf(false) }
+ val uri = remember { Uri.parse(music.mediaMetadata.extras?.getString("uri")) }
+ val path = remember { music.mediaMetadata.extras?.getString("path") }
val isPlaying = currentMusicUri == music.mediaMetadata.extras?.getString("uri")
val bgColor by animateColorAsState(
targetValue = if (isPlaying) {
@@ -300,84 +345,183 @@ fun MusicListItem(
label = "Background Color",
animationSpec = tween(500)
)
+ val deleteSongLauncher =
+ rememberLauncherForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
+ if (it.resultCode == Activity.RESULT_OK) {
+ Toast.makeText(
+ context,
+ context.resources.getText(R.string.deleting_song_OK),
+ Toast.LENGTH_SHORT
+ ).show()
-
- if (isSheetOpen) {
- ModalBottomSheet(
- modifier = Modifier.fillMaxHeight(),
- sheetState = sheetState,
- onDismissRequest = { isSheetOpen = false },
- ) {
- BottomSheetContent(
- music = music,
- onNavigate = { onNavigate(it) },
- onDismiss = { isSheetOpen = false },
- onLoadMetadata = onLoadMetadata,
- onDeleteMusic = onDeleteMusic!!
- )
+ } else {
+ Toast.makeText(
+ context,
+ context.resources.getText(R.string.error_deleting_song),
+ Toast.LENGTH_SHORT
+ ).show()
+ }
}
+
+ if (showDetailsDialog) {
+ MusicDetailsDialog(
+ music = music,
+ onDismissRequest = { showDetailsDialog = false }
+ )
}
+
+ Row(
+ modifier = modifier
+ .fillMaxWidth()
+ .clip(RoundedCornerShape(24.dp))
+ .combinedClickable(
+ onClick = { onShortClick(music.mediaId) }
+ )
+ .background(
+ color = bgColor,
+ shape = RoundedCornerShape(24.dp)
+ ),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+
Row(
- modifier = modifier
- .fillMaxWidth()
- .clip(RoundedCornerShape(24.dp))
- .combinedClickable(
- onClick = { onShortClick(music.mediaId) }
- )
- .background(
- color = bgColor,
- shape = RoundedCornerShape(24.dp)
- ),
verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.SpaceBetween
+ modifier = Modifier.weight(1f)
) {
+ AsyncImage(
+ model = ImageUtils.imageRequester(
+ img = music.mediaMetadata.artworkUri,
+ context = context
+ ),
+ stringResource(R.string.artwork),
+ modifier = Modifier
+ .padding(start = 10.dp)
+ .size(45.dp),
+ contentScale = ContentScale.Crop,
+ )
- Row(
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier.weight(1f)
+ Column(
+ modifier = Modifier.padding(15.dp)
) {
- AsyncImage(
- model = ImageUtils.imageRequester(
- img = music.mediaMetadata.artworkUri,
- context = context
- ),
- stringResource(R.string.artwork),
- modifier = Modifier
- .padding(start = 10.dp)
- .size(45.dp),
- contentScale = ContentScale.Crop,
+ CuteText(
+ text = music.mediaMetadata.title.toString(),
+ maxLines = 1,
+ modifier = Modifier.basicMarquee()
+ )
+ CuteText(
+ text = music.mediaMetadata.artist.toString(),
+ maxLines = 1,
+ color = MaterialTheme.colorScheme.onBackground.copy(0.85f)
)
-
- Column(
- modifier = Modifier.padding(15.dp)
- ) {
- CuteText(
- text = music.mediaMetadata.title.toString(),
- maxLines = 1,
- modifier = Modifier.basicMarquee()
- )
- CuteText(
- text = music.mediaMetadata.artist.toString(),
-
- maxLines = 1,
- color = MaterialTheme.colorScheme.onBackground.copy(0.85f)
- )
- }
}
+ }
- if (showBottomSheet) {
+ if (showBottomSheet) {
+ Row {
IconButton(
- onClick = { isSheetOpen = true }
+ onClick = { isDropDownExpanded = true }
) {
Icon(
imageVector = Icons.Rounded.MoreVert,
contentDescription = null
)
}
+ DropdownMenu(
+ expanded = isDropDownExpanded,
+ onDismissRequest = { isDropDownExpanded = false },
+ shape = RoundedCornerShape(24.dp)
+ ) {
+ DropdownMenuItem(
+ onClick = { showDetailsDialog = true },
+ text = {
+ CuteText(stringResource(R.string.details))
+ },
+ leadingIcon = {
+ Icon(
+ imageVector = Icons.Rounded.Info,
+ contentDescription = null
+ )
+ }
+ )
+ DropdownMenuItem(
+ onClick = {
+ isDropDownExpanded = false
+ onLoadMetadata(path ?: "")
+ onNavigate(Screen.MetadataEditor(music.mediaId))
+ },
+ text = {
+ CuteText(stringResource(R.string.edit))
+ },
+ leadingIcon = {
+ Icon(
+ imageVector = Icons.Rounded.Edit,
+ contentDescription = null
+ )
+ }
+ )
+ DropdownMenuItem(
+ onClick = {
+ isDropDownExpanded = false
+ onChargeAlbumSongs(music.mediaMetadata.albumTitle.toString())
+ onNavigate(
+ Screen.AlbumsDetails(
+ music.mediaMetadata.extras?.getLong("album_id") ?: 0
+ )
+ )
+ },
+ text = {
+ CuteText("Go to: ${music.mediaMetadata.albumTitle}")
+ },
+ leadingIcon = {
+ Icon(
+ imageVector = Icons.Rounded.Album,
+ contentDescription = null
+ )
+ }
+ )
+ DropdownMenuItem(
+ onClick = {
+ isDropDownExpanded = false
+ onChargeArtistLists(music.mediaMetadata.artist.toString())
+ onNavigate(
+ Screen.ArtistsDetails(
+ music.mediaMetadata.extras?.getLong("artist_id") ?: 0
+ )
+ )
+ },
+ text = {
+ CuteText("Go to: ${music.mediaMetadata.artist}")
+ },
+ leadingIcon = {
+ Icon(
+ imageVector = Icons.Rounded.Person,
+ contentDescription = null
+ )
+ }
+ )
+ DropdownMenuItem(
+ onClick = { onDeleteMusic(listOf(uri), deleteSongLauncher) },
+ text = {
+ CuteText(
+ text = stringResource(R.string.delete),
+ color = MaterialTheme.colorScheme.error
+ )
+ },
+ leadingIcon = {
+ Icon(
+ imageVector = Icons.Rounded.Delete,
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.error
+ )
+ }
+ )
+ }
}
}
+ }
}
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/screens/main/components/BottomSheetContent.kt b/app/src/main/java/com/sosauce/cutemusic/ui/screens/main/components/BottomSheetContent.kt
deleted file mode 100644
index 92418fc..0000000
--- a/app/src/main/java/com/sosauce/cutemusic/ui/screens/main/components/BottomSheetContent.kt
+++ /dev/null
@@ -1,243 +0,0 @@
-package com.sosauce.cutemusic.ui.screens.main.components
-
-import android.app.Activity
-import android.content.Context
-import android.media.MediaMetadataRetriever
-import android.net.Uri
-import android.widget.Toast
-import androidx.activity.compose.rememberLauncherForActivityResult
-import androidx.activity.result.ActivityResultLauncher
-import androidx.activity.result.IntentSenderRequest
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.compose.foundation.basicMarquee
-import androidx.compose.foundation.clickable
-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.shape.RoundedCornerShape
-import androidx.compose.material3.Card
-import androidx.compose.material3.CardDefaults
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
-import androidx.media3.common.MediaItem
-import coil3.compose.AsyncImage
-import com.sosauce.cutemusic.R
-import com.sosauce.cutemusic.ui.customs.formatBinarySize
-import com.sosauce.cutemusic.ui.navigation.Screen
-import com.sosauce.cutemusic.ui.shared_components.CuteText
-import com.sosauce.cutemusic.utils.ImageUtils
-
-@Composable
-fun BottomSheetContent(
- music: MediaItem,
- onNavigate: (Screen) -> Unit,
- onDismiss: () -> Unit,
- onLoadMetadata: ((String) -> Unit)? = null,
- onDeleteMusic: (List, ActivityResultLauncher) -> Unit
-) {
- val context = LocalContext.current
- val fileBitrate =
- getFileBitrate(context, Uri.parse(music.mediaMetadata.extras?.getString("uri")))
- val fileType =
- context.contentResolver.getType(Uri.parse(music.mediaMetadata.extras?.getString("uri")))
- val uri = Uri.parse(music.mediaMetadata.extras?.getString("uri"))
- val path = music.mediaMetadata.extras?.getString("path")
-
- val deleteSongLauncher =
- rememberLauncherForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
- if (it.resultCode == Activity.RESULT_OK) {
- Toast.makeText(
- context,
- context.resources.getText(R.string.deleting_song_OK),
- Toast.LENGTH_SHORT
- ).show()
-
- } else {
- Toast.makeText(
- context,
- context.resources.getText(R.string.error_deleting_song),
- Toast.LENGTH_SHORT
- ).show()
- }
- }
-
- Column {
- Card(
- modifier = Modifier
- .fillMaxWidth()
- .padding(horizontal = 16.dp, vertical = 2.dp),
- shape = RoundedCornerShape(
- topStart = 24.dp,
- topEnd = 24.dp,
- bottomStart = 4.dp,
- bottomEnd = 4.dp
- ),
- colors = CardDefaults.cardColors(
- MaterialTheme.colorScheme.surfaceContainerHighest.copy(
- alpha = 0.5f
- )
- )
- ) {
- Row(verticalAlignment = Alignment.CenterVertically) {
- AsyncImage(
- model = ImageUtils.imageRequester(
- img = music.mediaMetadata.artworkUri,
- context = context
- ),
- contentDescription = null,
- modifier = Modifier
- .size(100.dp)
- .padding(15.dp)
- .clip(RoundedCornerShape(15)),
- contentScale = ContentScale.Crop
-
- )
- Column {
- CuteText(
- text = music.mediaMetadata.title.toString(),
- modifier = Modifier
- .basicMarquee()
- )
- CuteText(
- text = music.mediaMetadata.artist.toString(),
- color = MaterialTheme.colorScheme.onBackground.copy(0.85f),
- modifier = Modifier.basicMarquee()
- )
- }
- }
- }
-
- Row(
- modifier = Modifier.padding(horizontal = 16.dp, vertical = 2.dp)
- ) {
- Card(
- modifier = Modifier
- .padding(bottom = 5.dp)
- .weight(1f)
- .clip(
- RoundedCornerShape(
- topStart = 4.dp,
- topEnd = 4.dp,
- bottomStart = 24.dp,
- bottomEnd = 4.dp
- )
- )
- .clickable {
- onLoadMetadata?.let { it(path.toString()) }
- onDismiss()
- onNavigate(Screen.MetadataEditor(music.mediaId))
- },
- colors = CardDefaults.cardColors(
- containerColor = MaterialTheme.colorScheme.surfaceContainerHighest.copy(
- alpha = 0.5f
- )
- ),
- shape = RoundedCornerShape(
- topStart = 4.dp,
- topEnd = 4.dp,
- bottomStart = 24.dp,
- bottomEnd = 4.dp
- ),
- ) {
- Row(
- modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.Center,
- verticalAlignment = Alignment.CenterVertically
- ) {
- CuteText(
- text = stringResource(R.string.edit),
- modifier = Modifier.padding(10.dp)
- )
- }
- }
- Card(
- modifier = Modifier
- .padding(start = 5.dp)
- .weight(1f)
- .clip(
- RoundedCornerShape(
- topStart = 4.dp,
- topEnd = 4.dp,
- bottomStart = 4.dp,
- bottomEnd = 24.dp
- )
- )
- .clickable { onDeleteMusic(
- listOf(uri),
- deleteSongLauncher
- ) },
- colors = CardDefaults.cardColors(
- containerColor = MaterialTheme.colorScheme.surfaceContainerHighest.copy(
- alpha = 0.5f
- )
- ),
- shape = RoundedCornerShape(
- topStart = 4.dp,
- topEnd = 4.dp,
- bottomStart = 4.dp,
- bottomEnd = 24.dp
- ),
- ) {
- Row(
- modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.Center,
- verticalAlignment = Alignment.CenterVertically
- ) {
- CuteText(
- text = stringResource(R.string.delete),
- color = MaterialTheme.colorScheme.error,
- modifier = Modifier.padding(10.dp)
- )
- }
- }
- }
-
- Column(
- modifier = Modifier.padding(start = 16.dp, top = 16.dp)
- ) {
- CuteText(
- text = "${stringResource(id = R.string.size)}: ${
- music.mediaMetadata.extras?.getLong(
- "size"
- )?.formatBinarySize()
- }",
- modifier = Modifier.padding(bottom = 5.dp)
- )
- CuteText(
- text = "${stringResource(id = R.string.bitrate)}: $fileBitrate",
-
- modifier = Modifier.padding(bottom = 5.dp)
- )
- CuteText(
- text = "${stringResource(id = R.string.type)}: $fileType",
-
- modifier = Modifier.padding(bottom = 5.dp)
- )
- }
-
- }
-}
-
-private fun getFileBitrate(context: Context, uri: Uri): String {
- val retriever = MediaMetadataRetriever()
- return try {
- retriever.setDataSource(context, uri)
- val bitrate = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE)
- bitrate?.toInt()?.div(1000)?.toString()?.plus(" kbps") ?: "Unknown"
- } catch (e: Exception) {
- "Unknown"
- } finally {
- retriever.release()
- }
-}
-
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 93657dc..5b1f719 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
@@ -110,10 +110,12 @@ fun MetadataEditorContent(
Scaffold(
floatingActionButton = {
FloatingActionButton(
- onClick = { onEditMusic(
- listOf(uri),
- editSongLauncher
- ) }
+ onClick = {
+ onEditMusic(
+ listOf(uri),
+ editSongLauncher
+ )
+ }
) {
Icon(
imageVector = Icons.Default.Done,
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 1c52055..e6983af 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
@@ -6,8 +6,6 @@ import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionScope
import androidx.compose.animation.core.animateIntAsState
-import androidx.compose.animation.core.tween
-import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -22,16 +20,7 @@ import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width
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.filled.KeyboardArrowDown
-import androidx.compose.material.icons.rounded.FastForward
-import androidx.compose.material.icons.rounded.FastRewind
-import androidx.compose.material.icons.rounded.Pause
-import androidx.compose.material.icons.rounded.PlayArrow
-import androidx.compose.material.icons.rounded.SkipNext
-import androidx.compose.material.icons.rounded.SkipPrevious
-import androidx.compose.material.icons.rounded.Speed
-import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@@ -47,44 +36,32 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import androidx.navigation.NavController
import coil3.compose.AsyncImage
import com.sosauce.cutemusic.R
+import com.sosauce.cutemusic.data.MusicState
import com.sosauce.cutemusic.data.actions.PlayerActions
import com.sosauce.cutemusic.data.datastore.rememberSnapSpeedAndPitch
+import com.sosauce.cutemusic.ui.navigation.Screen
import com.sosauce.cutemusic.ui.screens.lyrics.LyricsView
import com.sosauce.cutemusic.ui.screens.playing.components.ActionsButtonsRow
-import com.sosauce.cutemusic.ui.screens.playing.components.LoopButton
import com.sosauce.cutemusic.ui.screens.playing.components.MusicSlider
-import com.sosauce.cutemusic.ui.screens.playing.components.ShuffleButton
+import com.sosauce.cutemusic.ui.screens.playing.components.QuickActionsRow
import com.sosauce.cutemusic.ui.screens.playing.components.SpeedCard
import com.sosauce.cutemusic.ui.shared_components.CuteText
import com.sosauce.cutemusic.ui.shared_components.MusicViewModel
@Composable
fun SharedTransitionScope.NowPlayingLandscape(
- viewModel: MusicViewModel,
- navController: NavController,
- animatedVisibilityScope: AnimatedVisibilityScope,
-) {
- NPLContent(
- viewModel = viewModel,
- onEvent = viewModel::handlePlayerActions,
- onNavigateUp = navController::navigateUp,
- onClickLoop = { viewModel.setLoop(it) },
- onClickShuffle = { viewModel.setShuffle(it) },
- animatedVisibilityScope = animatedVisibilityScope,
- )
-}
-
-@Composable
-private fun SharedTransitionScope.NPLContent(
viewModel: MusicViewModel,
onNavigateUp: () -> Unit,
onEvent: (PlayerActions) -> Unit,
- onClickLoop: (Boolean) -> Unit,
- onClickShuffle: (Boolean) -> Unit,
+ onClickLoop: () -> Unit,
+ onClickShuffle: () -> Unit,
animatedVisibilityScope: AnimatedVisibilityScope,
+ musicState: MusicState,
+ onChargeAlbumSongs: (String) -> Unit,
+ onNavigate: (Screen) -> Unit,
+ onChargeArtistLists: (String) -> Unit
) {
var showSpeedCard by remember { mutableStateOf(false) }
var showLyrics by remember { mutableStateOf(false) }
@@ -106,112 +83,104 @@ private fun SharedTransitionScope.NPLContent(
)
Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .statusBarsPadding()
+ .navigationBarsPadding()
+ .padding(start = 30.dp, end = 30.dp)
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxSize()
- .statusBarsPadding()
- .navigationBarsPadding()
- .padding(start = 30.dp, end = 30.dp)
) {
- Row(
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier
- .fillMaxSize()
- ) {
- Column {
- AsyncImage(
- model = viewModel.currentArt,
- stringResource(R.string.artwork),
- modifier = Modifier
- .size(imgSize.dp)
- .clip(RoundedCornerShape(10)),
- contentScale = ContentScale.Crop
- )
- if (showLyrics) {
- Spacer(Modifier.height(10.dp))
- CuteText(
- text = viewModel.currentlyPlaying
- )
- CuteText(
- text = viewModel.currentArtist
- )
- }
- }
- Spacer(modifier = Modifier.width(10.dp))
+ Column {
+ AsyncImage(
+ model = musicState.currentArt,
+ stringResource(R.string.artwork),
+ modifier = Modifier
+ .size(imgSize.dp)
+ .clip(RoundedCornerShape(10)),
+ contentScale = ContentScale.Crop
+ )
if (showLyrics) {
- LyricsView(
- viewModel = viewModel,
- onHideLyrics = { showLyrics = false },
- path = viewModel.currentPath,
- isLandscape = true
+ Spacer(Modifier.height(10.dp))
+ CuteText(
+ text = musicState.currentlyPlaying
+ )
+ CuteText(
+ text = musicState.currentArtist
)
- } else {
- Column(
- modifier = Modifier
+ }
+ }
+ Spacer(modifier = Modifier.width(10.dp))
+ if (showLyrics) {
+ LyricsView(
+ viewModel = viewModel,
+ onHideLyrics = { showLyrics = false },
+ isLandscape = true,
+ musicState = musicState
+ )
+ } else {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Row(
+ Modifier
.fillMaxWidth(),
- horizontalAlignment = Alignment.CenterHorizontally
+ verticalAlignment = Alignment.CenterVertically
) {
- Row(
- Modifier
- .fillMaxWidth(),
- verticalAlignment = Alignment.CenterVertically
+ IconButton(
+ onClick = { onNavigateUp() }
) {
- IconButton(
- onClick = { onNavigateUp() }
- ) {
- Icon(
- imageVector = Icons.Default.KeyboardArrowDown,
- contentDescription = null,
- modifier = Modifier.size(28.dp)
- )
- }
- Box(
- contentAlignment = Alignment.Center,
- modifier = Modifier.fillMaxWidth(0.9f)
+ Icon(
+ imageVector = Icons.Default.KeyboardArrowDown,
+ contentDescription = null,
+ modifier = Modifier.size(28.dp)
)
- {
- CuteText(
- text = viewModel.currentlyPlaying,
- color = MaterialTheme.colorScheme.onBackground,
- fontSize = 30.sp
- )
- }
}
- CuteText(
- text = viewModel.currentArtist,
- color = MaterialTheme.colorScheme.onBackground.copy(0.85f),
- fontSize = 16.sp
+ Box(
+ contentAlignment = Alignment.Center,
+ modifier = Modifier.fillMaxWidth(0.9f)
)
- MusicSlider(viewModel = viewModel)
- ActionsButtonsRow(
- onClickLoop = onClickLoop,
- onClickShuffle = onClickShuffle,
- viewModel = viewModel,
- onEvent = onEvent,
- animatedVisibilityScope = animatedVisibilityScope
- )
- Spacer(modifier = Modifier.weight(1f))
- Row(
- horizontalArrangement = Arrangement.Center,
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier
- .fillMaxWidth()
- ) {
- IconButton(onClick = { showSpeedCard = true }) {
- Icon(
- imageVector = Icons.Rounded.Speed,
- contentDescription = "change speed"
- )
- }
- IconButton(onClick = { showLyrics = true }) {
- Icon(
- imageVector = Icons.AutoMirrored.Rounded.Article,
- contentDescription = "show lyrics"
- )
- }
+ {
+ CuteText(
+ text = musicState.currentlyPlaying,
+ color = MaterialTheme.colorScheme.onBackground,
+ fontSize = 30.sp
+ )
}
}
+ CuteText(
+ text = musicState.currentArtist,
+ color = MaterialTheme.colorScheme.onBackground.copy(0.85f),
+ fontSize = 16.sp
+ )
+ MusicSlider(
+ viewModel = viewModel,
+ musicState = musicState
+ )
+ ActionsButtonsRow(
+ onClickLoop = onClickLoop,
+ onClickShuffle = onClickShuffle,
+ onEvent = onEvent,
+ animatedVisibilityScope = animatedVisibilityScope,
+ musicState = musicState
+ )
+ Spacer(modifier = Modifier.weight(1f))
+ QuickActionsRow(
+ musicState = musicState,
+ onNavigate = onNavigate,
+ onShowLyrics = { showLyrics = true },
+ onChargeAlbumSongs = onChargeAlbumSongs,
+ onShowSpeedCard = { showSpeedCard = true },
+ onChargeArtistLists = onChargeArtistLists
+ )
}
-
}
+
}
+ }
}
\ No newline at end of file
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 71e7b71..519497b 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
@@ -3,42 +3,27 @@
package com.sosauce.cutemusic.ui.screens.playing
-import androidx.annotation.OptIn
import androidx.compose.animation.AnimatedVisibilityScope
-import androidx.compose.animation.Crossfade
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionScope
-import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.animateIntAsState
import androidx.compose.animation.core.tween
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.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
-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.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.automirrored.rounded.Article
-import androidx.compose.material.icons.rounded.FastForward
-import androidx.compose.material.icons.rounded.FastRewind
import androidx.compose.material.icons.rounded.KeyboardArrowDown
-import androidx.compose.material.icons.rounded.Pause
-import androidx.compose.material.icons.rounded.PlayArrow
-import androidx.compose.material.icons.rounded.RestartAlt
-import androidx.compose.material.icons.rounded.SkipNext
-import androidx.compose.material.icons.rounded.SkipPrevious
-import androidx.compose.material.icons.rounded.Speed
-import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -52,38 +37,47 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import androidx.media3.common.util.UnstableApi
import androidx.navigation.NavController
import coil3.compose.AsyncImage
import com.sosauce.cutemusic.R
+import com.sosauce.cutemusic.data.MusicState
import com.sosauce.cutemusic.data.actions.PlayerActions
import com.sosauce.cutemusic.data.datastore.rememberIsLandscape
import com.sosauce.cutemusic.data.datastore.rememberSnapSpeedAndPitch
+import com.sosauce.cutemusic.ui.navigation.Screen
import com.sosauce.cutemusic.ui.screens.lyrics.LyricsView
import com.sosauce.cutemusic.ui.screens.playing.components.ActionsButtonsRow
-import com.sosauce.cutemusic.ui.screens.playing.components.LoopButton
import com.sosauce.cutemusic.ui.screens.playing.components.MusicSlider
-import com.sosauce.cutemusic.ui.screens.playing.components.ShuffleButton
+import com.sosauce.cutemusic.ui.screens.playing.components.QuickActionsRow
import com.sosauce.cutemusic.ui.screens.playing.components.SpeedCard
import com.sosauce.cutemusic.ui.shared_components.CuteText
import com.sosauce.cutemusic.ui.shared_components.MusicViewModel
import com.sosauce.cutemusic.utils.ImageUtils
-@OptIn(UnstableApi::class)
@Composable
fun SharedTransitionScope.NowPlayingScreen(
navController: NavController,
viewModel: MusicViewModel,
animatedVisibilityScope: AnimatedVisibilityScope,
+ musicState: MusicState,
+ onChargeAlbumSongs: (String) -> Unit,
+ onChargeArtistLists: (String) -> Unit
) {
var showFullLyrics by remember { mutableStateOf(false) }
if (rememberIsLandscape()) {
NowPlayingLandscape(
viewModel = viewModel,
- navController = navController,
+ onNavigateUp = navController::navigateUp,
+ onEvent = { viewModel.handlePlayerActions(it) },
+ onClickLoop = { viewModel.handlePlayerActions(PlayerActions.ApplyLoop) },
+ onClickShuffle = { viewModel.handlePlayerActions(PlayerActions.ApplyShuffle) },
animatedVisibilityScope = animatedVisibilityScope,
+ musicState = musicState,
+ onChargeAlbumSongs = onChargeAlbumSongs,
+ onNavigate = { navController.navigate(it) },
+ onChargeArtistLists = onChargeArtistLists
)
} else {
when (showFullLyrics) {
@@ -91,7 +85,7 @@ fun SharedTransitionScope.NowPlayingScreen(
LyricsView(
viewModel = viewModel,
onHideLyrics = { showFullLyrics = false },
- path = viewModel.currentPath
+ musicState = musicState
)
}
@@ -100,10 +94,14 @@ fun SharedTransitionScope.NowPlayingScreen(
viewModel = viewModel,
onEvent = viewModel::handlePlayerActions,
onNavigateUp = navController::navigateUp,
- onClickLoop = { viewModel.setLoop(it) },
- onClickShuffle = { viewModel.setShuffle(it) },
+ onClickLoop = { viewModel.handlePlayerActions(PlayerActions.ApplyLoop) },
+ onClickShuffle = { viewModel.handlePlayerActions(PlayerActions.ApplyShuffle) },
animatedVisibilityScope = animatedVisibilityScope,
onShowLyrics = { showFullLyrics = true },
+ musicState = musicState,
+ onChargeAlbumSongs = onChargeAlbumSongs,
+ onNavigate = { navController.navigate(it) },
+ onChargeArtistLists = onChargeArtistLists
)
}
}
@@ -116,10 +114,14 @@ private fun SharedTransitionScope.NowPlayingContent(
viewModel: MusicViewModel,
onEvent: (PlayerActions) -> Unit,
onNavigateUp: () -> Unit,
- onClickLoop: (Boolean) -> Unit,
- onClickShuffle: (Boolean) -> Unit,
+ onClickLoop: () -> Unit,
+ onClickShuffle: () -> Unit,
animatedVisibilityScope: AnimatedVisibilityScope,
onShowLyrics: () -> Unit,
+ musicState: MusicState,
+ onChargeAlbumSongs: (String) -> Unit,
+ onNavigate: (Screen) -> Unit,
+ onChargeArtistLists: (String) -> Unit
) {
val context = LocalContext.current
var showSpeedCard by remember { mutableStateOf(false) }
@@ -135,121 +137,109 @@ private fun SharedTransitionScope.NowPlayingContent(
onChangeSnap = { snap = !snap }
)
}
- Scaffold(
- modifier = Modifier.padding(15.dp)
- ) { _ ->
- Column(
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(15.dp)
+ .statusBarsPadding(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center
+ ) {
+ Row(
modifier = Modifier
.fillMaxWidth()
- .statusBarsPadding(),
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.Center
+ .padding(15.dp),
+ horizontalArrangement = Arrangement.Start
) {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .padding(15.dp),
- horizontalArrangement = Arrangement.Start
- ) {
- IconButton(
- onClick = onNavigateUp
- ) {
- Icon(
- imageVector = Icons.Rounded.KeyboardArrowDown,
- contentDescription = null,
- modifier = Modifier
- .sharedElement(
- state = rememberSharedContentState(key = "arrow"),
- animatedVisibilityScope = animatedVisibilityScope,
- boundsTransform = { _, _ ->
- tween(durationMillis = 500)
- }
- )
- .size(28.dp)
- )
- }
- }
- AsyncImage(
- model = ImageUtils.imageRequester(
- img = viewModel.currentArt,
- context = context
- ),
- contentDescription = stringResource(R.string.artwork),
- modifier = Modifier
- .size(340.dp)
- .clip(RoundedCornerShape(5)),
- contentScale = ContentScale.Crop
- )
-
- Spacer(modifier = Modifier.height(20.dp))
-
- Row(
- modifier = Modifier.fillMaxWidth(),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.Center
+ IconButton(
+ onClick = onNavigateUp
) {
- Column(
+ Icon(
+ imageVector = Icons.Rounded.KeyboardArrowDown,
+ contentDescription = null,
modifier = Modifier
- .padding(horizontal = 15.dp),
- horizontalAlignment = Alignment.CenterHorizontally,
- ) {
-
- CuteText(
- text = viewModel.currentlyPlaying,
- color = MaterialTheme.colorScheme.onBackground,
- fontSize = 20.sp,
- modifier = Modifier
- .sharedElement(
- state = rememberSharedContentState(key = "currentlyPlaying"),
- animatedVisibilityScope = animatedVisibilityScope,
- boundsTransform = { _, _ ->
- tween(durationMillis = 500)
- }
- )
- .basicMarquee()
- )
- //Spacer(modifier = Modifier.height(5.dp))
- CuteText(
- text = viewModel.currentArtist,
- color = MaterialTheme.colorScheme.onBackground.copy(0.85f),
- fontSize = 14.sp,
- modifier = Modifier.basicMarquee()
- )
- }
+ .sharedElement(
+ state = rememberSharedContentState(key = "arrow"),
+ animatedVisibilityScope = animatedVisibilityScope,
+ boundsTransform = { _, _ ->
+ tween(durationMillis = 500)
+ }
+ )
+ .size(28.dp)
+ )
}
- Spacer(modifier = Modifier.height(10.dp))
+ }
+ AsyncImage(
+ model = ImageUtils.imageRequester(
+ img = musicState.currentArt,
+ context = context
+ ),
+ contentDescription = stringResource(R.string.artwork),
+ modifier = Modifier
+ .size(340.dp)
+ .clip(RoundedCornerShape(5)),
+ contentScale = ContentScale.Crop
+ )
+
+ Spacer(modifier = Modifier.height(20.dp))
- MusicSlider(viewModel = viewModel)
- Spacer(modifier = Modifier.height(7.dp))
- ActionsButtonsRow(
- onClickLoop = onClickLoop,
- onClickShuffle = onClickShuffle,
- viewModel = viewModel,
- onEvent = onEvent,
- animatedVisibilityScope = animatedVisibilityScope
- )
- Spacer(modifier = Modifier.weight(1f))
- Row(
- horizontalArrangement = Arrangement.Center,
- verticalAlignment = Alignment.CenterVertically,
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Center
+ ) {
+ Column(
modifier = Modifier
- .fillMaxWidth()
- .navigationBarsPadding()
+ .padding(horizontal = 15.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
) {
- IconButton(onClick = onShowLyrics) {
- Icon(
- imageVector = Icons.AutoMirrored.Rounded.Article,
- contentDescription = "show lyrics"
- )
- }
- IconButton(onClick = { showSpeedCard = true }) {
- Icon(
- imageVector = Icons.Rounded.Speed,
- contentDescription = "change speed"
- )
- }
+
+ CuteText(
+ text = musicState.currentlyPlaying,
+ color = MaterialTheme.colorScheme.onBackground,
+ fontSize = 20.sp,
+ modifier = Modifier
+ .sharedElement(
+ state = rememberSharedContentState(key = "currentlyPlaying"),
+ animatedVisibilityScope = animatedVisibilityScope,
+ boundsTransform = { _, _ ->
+ tween(durationMillis = 500)
+ }
+ )
+ .basicMarquee()
+ )
+ //Spacer(modifier = Modifier.height(5.dp))
+ CuteText(
+ text = musicState.currentArtist,
+ color = MaterialTheme.colorScheme.onBackground.copy(0.85f),
+ fontSize = 14.sp,
+ modifier = Modifier.basicMarquee()
+ )
}
}
+ Spacer(modifier = Modifier.height(10.dp))
+
+ MusicSlider(
+ viewModel = viewModel,
+ musicState = musicState
+ )
+ Spacer(modifier = Modifier.height(7.dp))
+ ActionsButtonsRow(
+ onClickLoop = onClickLoop,
+ onClickShuffle = onClickShuffle,
+ onEvent = onEvent,
+ animatedVisibilityScope = animatedVisibilityScope,
+ musicState = musicState
+ )
+ Spacer(modifier = Modifier.weight(1f))
+ QuickActionsRow(
+ musicState = musicState,
+ onNavigate = onNavigate,
+ onShowLyrics = onShowLyrics,
+ onChargeAlbumSongs = onChargeAlbumSongs,
+ onShowSpeedCard = { showSpeedCard = true },
+ onChargeArtistLists = onChargeArtistLists
+ )
}
}
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/NowPlayingState.kt b/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/NowPlayingState.kt
deleted file mode 100644
index f7c82b0..0000000
--- a/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/NowPlayingState.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.sosauce.cutemusic.ui.screens.playing
-
-import androidx.media3.common.MediaItem
-
-data class NowPlayingState(
- var currentlyPlaying: String = "",
- var currentlyArtist: String = "",
- var currentMusicDuration: Long = 0L,
- var currentPosition: Long = 0L,
- var isPlaying: Boolean = false,
- var artwork: ByteArray? = null,
- val musics: List? = emptyList()
-)
\ No newline at end of file
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 fbdf085..bed1b14 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
@@ -2,8 +2,7 @@
package com.sosauce.cutemusic.ui.screens.playing.components
-import android.renderscript.RenderScript
-import androidx.compose.animation.AnimatedVisibility
+import android.util.Log
import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.Crossfade
import androidx.compose.animation.ExperimentalSharedTransitionApi
@@ -12,21 +11,13 @@ import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.animateIntAsState
import androidx.compose.animation.core.tween
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
-import androidx.compose.animation.slideInVertically
-import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
-import androidx.compose.foundation.combinedClickable
-import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.FastForward
@@ -38,7 +29,6 @@ import androidx.compose.material.icons.rounded.RestartAlt
import androidx.compose.material.icons.rounded.Shuffle
import androidx.compose.material.icons.rounded.SkipNext
import androidx.compose.material.icons.rounded.SkipPrevious
-import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@@ -51,34 +41,31 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.blur
-import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
-import androidx.compose.ui.window.Popup
-import androidx.compose.ui.window.PopupProperties
+import com.sosauce.cutemusic.data.MusicState
import com.sosauce.cutemusic.data.actions.PlayerActions
+import com.sosauce.cutemusic.data.datastore.rememberShouldApplyLoop
import com.sosauce.cutemusic.ui.shared_components.CuteText
-import com.sosauce.cutemusic.ui.shared_components.MusicViewModel
import com.sosauce.cutemusic.utils.CuteIconButton
-import com.sosauce.cutemusic.utils.thenIf
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@Composable
fun LoopButton(
- onClick: (Boolean) -> Unit,
+ onClick: () -> Unit,
isLooping: Boolean
) {
+
val rotation = remember { Animatable(0f) }
val scope = rememberCoroutineScope()
+ var shouldApplyLoop by rememberShouldApplyLoop()
IconButton(
onClick = {
- onClick(!isLooping)
+ shouldApplyLoop = !isLooping
+ onClick()
scope.launch(Dispatchers.IO) {
rotation.animateTo(
targetValue = -360f,
@@ -90,6 +77,8 @@ fun LoopButton(
animationSpec = tween(0)
)
}
+ Log.d("Looping2", shouldApplyLoop.toString())
+
}
) {
Icon(
@@ -103,15 +92,13 @@ fun LoopButton(
@Composable
fun ShuffleButton(
- onClick: (Boolean) -> Unit,
+ onClick: () -> Unit,
isShuffling: Boolean
) {
IconButton(
- onClick = {
- onClick(!isShuffling)
- }
+ onClick = onClick
) {
Icon(
imageVector = Icons.Rounded.Shuffle,
@@ -123,11 +110,11 @@ fun ShuffleButton(
@Composable
fun SharedTransitionScope.ActionsButtonsRow(
- onClickLoop: (Boolean) -> Unit,
- onClickShuffle: (Boolean) -> Unit,
- viewModel: MusicViewModel,
+ onClickLoop: () -> Unit,
+ onClickShuffle: () -> Unit,
onEvent: (PlayerActions) -> Unit,
- animatedVisibilityScope: AnimatedVisibilityScope
+ animatedVisibilityScope: AnimatedVisibilityScope,
+ musicState: MusicState
) {
@@ -148,7 +135,7 @@ fun SharedTransitionScope.ActionsButtonsRow(
)
val roundedFAB by animateIntAsState(
- targetValue = if (viewModel.isCurrentlyPlaying) 30 else 50,
+ targetValue = if (musicState.isCurrentlyPlaying) 30 else 50,
label = "FAB Shape"
)
@@ -181,7 +168,7 @@ fun SharedTransitionScope.ActionsButtonsRow(
} else {
ShuffleButton(
onClick = onClickShuffle,
- isShuffling = viewModel.isShuffling
+ isShuffling = musicState.isShuffling
)
}
}
@@ -200,7 +187,7 @@ fun SharedTransitionScope.ActionsButtonsRow(
} else {
IconButton(
onClick = {
- if (viewModel.currentPosition >= 10000) {
+ if (musicState.currentPosition >= 10000) {
onEvent(PlayerActions.RestartSong)
} else {
onEvent(PlayerActions.SeekToPreviousMusic)
@@ -218,7 +205,7 @@ fun SharedTransitionScope.ActionsButtonsRow(
}
) {
Crossfade(
- targetState = viewModel.currentPosition >= 10000,
+ targetState = musicState.currentPosition >= 10000,
label = ""
) {
if (!it) {
@@ -263,7 +250,7 @@ fun SharedTransitionScope.ActionsButtonsRow(
}
) { CuteText("-10") }
} else {
- CuteIconButton (
+ CuteIconButton(
onClick = { onEvent(PlayerActions.RewindTo(5000)) },
onLongClick = { showLongPressMenuMinus = true }
) {
@@ -280,7 +267,7 @@ fun SharedTransitionScope.ActionsButtonsRow(
shape = RoundedCornerShape(roundedFAB)
) {
Icon(
- imageVector = if (viewModel.isCurrentlyPlaying) Icons.Rounded.Pause else Icons.Rounded.PlayArrow,
+ imageVector = if (musicState.isCurrentlyPlaying) Icons.Rounded.Pause else Icons.Rounded.PlayArrow,
contentDescription = "pause/play button",
modifier = Modifier.sharedElement(
state = rememberSharedContentState(key = "playPauseIcon"),
@@ -388,7 +375,7 @@ fun SharedTransitionScope.ActionsButtonsRow(
} else {
LoopButton(
onClick = onClickLoop,
- isLooping = viewModel.isLooping
+ isLooping = musicState.isLooping
)
}
}
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
new file mode 100644
index 0000000..8a79021
--- /dev/null
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/components/QuickActionsRow.kt
@@ -0,0 +1,136 @@
+package com.sosauce.cutemusic.ui.screens.playing.components
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.navigationBarsPadding
+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.rounded.Album
+import androidx.compose.material.icons.rounded.Info
+import androidx.compose.material.icons.rounded.MoreVert
+import androidx.compose.material.icons.rounded.Person
+import androidx.compose.material.icons.rounded.Speed
+import androidx.compose.material3.DropdownMenu
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+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.res.stringResource
+import androidx.compose.ui.unit.dp
+import com.sosauce.cutemusic.R
+import com.sosauce.cutemusic.data.MusicState
+import com.sosauce.cutemusic.ui.navigation.Screen
+import com.sosauce.cutemusic.ui.shared_components.CuteText
+import com.sosauce.cutemusic.ui.shared_components.MusicStateDetailsDialog
+
+@Composable
+fun QuickActionsRow(
+ musicState: MusicState,
+ onShowLyrics: () -> Unit,
+ onShowSpeedCard: () -> Unit,
+ onChargeAlbumSongs: (String) -> Unit,
+ onNavigate: (Screen) -> Unit,
+ onChargeArtistLists: (String) -> Unit
+) {
+
+ var isDropDownExpanded by remember { mutableStateOf(false) }
+ var showDetailsDialog by remember { mutableStateOf(false) }
+
+ if (showDetailsDialog) {
+ MusicStateDetailsDialog(
+ musicState = musicState,
+ onDismissRequest = { showDetailsDialog = false }
+ )
+ }
+
+
+ Row(
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier
+ .fillMaxWidth()
+ .navigationBarsPadding()
+ ) {
+
+ IconButton(onClick = onShowLyrics) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Rounded.Article,
+ contentDescription = "show lyrics"
+ )
+ }
+ IconButton(onClick = onShowSpeedCard) {
+ Icon(
+ imageVector = Icons.Rounded.Speed,
+ contentDescription = "change speed"
+ )
+ }
+ Row {
+ IconButton(onClick = { isDropDownExpanded = true }) {
+ Icon(
+ imageVector = Icons.Rounded.MoreVert,
+ contentDescription = "more"
+ )
+ }
+
+
+ DropdownMenu(
+ expanded = isDropDownExpanded,
+ onDismissRequest = { isDropDownExpanded = false },
+ shape = RoundedCornerShape(24.dp)
+ ) {
+ DropdownMenuItem(
+ onClick = { showDetailsDialog = true },
+ text = {
+ CuteText(stringResource(R.string.details))
+ },
+ leadingIcon = {
+ Icon(
+ imageVector = Icons.Rounded.Info,
+ contentDescription = null
+ )
+ }
+ )
+ DropdownMenuItem(
+ onClick = {
+ isDropDownExpanded = false
+ onChargeAlbumSongs(musicState.currentAlbum)
+ onNavigate(Screen.AlbumsDetails(musicState.currentAlbumId))
+ },
+ text = {
+ CuteText("Go to: ${musicState.currentAlbum}")
+ },
+ leadingIcon = {
+ Icon(
+ imageVector = Icons.Rounded.Album,
+ contentDescription = null
+ )
+ }
+ )
+ DropdownMenuItem(
+ onClick = {
+ isDropDownExpanded = false
+ onChargeArtistLists(musicState.currentArtist)
+ onNavigate(Screen.ArtistsDetails(musicState.currentArtistId))
+ },
+ text = {
+ CuteText("Go to: ${musicState.currentArtist}")
+ },
+ leadingIcon = {
+ Icon(
+ imageVector = Icons.Rounded.Person,
+ contentDescription = null
+ )
+ }
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/components/Slider.kt b/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/components/Slider.kt
index b1681d1..a02cba1 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/components/Slider.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/components/Slider.kt
@@ -1,4 +1,4 @@
-@file:OptIn(ExperimentalMaterial3Api::class)
+@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3Api::class)
package com.sosauce.cutemusic.ui.screens.playing.components
@@ -9,27 +9,38 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Slider
+import androidx.compose.material3.SliderDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
+import com.sosauce.cutemusic.data.MusicState
import com.sosauce.cutemusic.data.actions.PlayerActions
+import com.sosauce.cutemusic.data.datastore.rememberUseClassicSlider
import com.sosauce.cutemusic.ui.shared_components.CuteText
import com.sosauce.cutemusic.ui.shared_components.MusicViewModel
import me.saket.squiggles.SquigglySlider
import java.util.Locale
@Composable
-fun MusicSlider(viewModel: MusicViewModel) {
+fun MusicSlider(
+ viewModel: MusicViewModel,
+ musicState: MusicState
+) {
- val sliderPosition = rememberUpdatedState(viewModel.currentPosition)
+ val sliderPosition = rememberUpdatedState(musicState.currentPosition)
+ val useClassicSlider by rememberUseClassicSlider()
+ val interactionSource = remember { MutableInteractionSource() }
Column {
Row(
@@ -46,7 +57,7 @@ fun MusicSlider(viewModel: MusicViewModel) {
.padding(horizontal = 8.dp, vertical = 4.dp)
) {
CuteText(
- text = totalDuration(viewModel.currentMusicDuration),
+ text = totalDuration(musicState.currentMusicDuration),
)
}
Box(
@@ -58,22 +69,51 @@ fun MusicSlider(viewModel: MusicViewModel) {
.padding(horizontal = 8.dp, vertical = 4.dp)
) {
CuteText(
- text = timeLeft(viewModel.currentPosition)
+ text = timeLeft(musicState.currentPosition)
)
}
}
- SquigglySlider(
- value = sliderPosition.value.toFloat(),
- onValueChange = {
- viewModel.currentPosition = it.toLong()
- viewModel.handlePlayerActions(PlayerActions.SeekToSlider(viewModel.currentPosition))
- },
- valueRange = 0f..viewModel.currentMusicDuration.toFloat(),
- onValueChangeFinished = {
- viewModel.handlePlayerActions(PlayerActions.SeekToSlider(viewModel.currentPosition))
- },
- modifier = Modifier.fillMaxWidth()
- )
+ if (useClassicSlider) {
+ Slider(
+ value = sliderPosition.value.toFloat(),
+ onValueChange = {
+ musicState.currentPosition = it.toLong()
+ viewModel.handlePlayerActions(PlayerActions.SeekToSlider(musicState.currentPosition))
+ },
+ valueRange = 0f..musicState.currentMusicDuration.toFloat(),
+ onValueChangeFinished = {
+ viewModel.handlePlayerActions(PlayerActions.SeekToSlider(musicState.currentPosition))
+ },
+ modifier = Modifier.fillMaxWidth(),
+ track = { sliderState ->
+ SliderDefaults.Track(
+ sliderState = sliderState,
+ drawStopIndicator = null,
+ thumbTrackGapSize = 0.dp,
+ modifier = Modifier.height(4.dp)
+ )
+ },
+ thumb = {
+ SliderDefaults.Thumb(
+ interactionSource = interactionSource,
+ thumbSize = DpSize(width = 20.dp, height = 20.dp)
+ )
+ }
+ )
+ } else {
+ SquigglySlider(
+ value = sliderPosition.value.toFloat(),
+ onValueChange = {
+ musicState.currentPosition = it.toLong()
+ viewModel.handlePlayerActions(PlayerActions.SeekToSlider(musicState.currentPosition))
+ },
+ valueRange = 0f..musicState.currentMusicDuration.toFloat(),
+ onValueChangeFinished = {
+ viewModel.handlePlayerActions(PlayerActions.SeekToSlider(musicState.currentPosition))
+ },
+ modifier = Modifier.fillMaxWidth()
+ )
+ }
}
}
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/components/SpeedCard.kt b/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/components/SpeedCard.kt
index ef48384..fc5dea3 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/components/SpeedCard.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/screens/playing/components/SpeedCard.kt
@@ -32,6 +32,7 @@ import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.sosauce.cutemusic.R
+import com.sosauce.cutemusic.data.actions.PlayerActions
import com.sosauce.cutemusic.ui.shared_components.CuteText
import com.sosauce.cutemusic.ui.shared_components.MusicViewModel
@@ -87,7 +88,12 @@ fun SpeedCard(
value = speed,
onValueChange = {
speed = it
- viewModel.setPlaybackSpeed(speed, pitch)
+ viewModel.handlePlayerActions(
+ PlayerActions.ApplyPlaybackSpeed(
+ speed = speed,
+ pitch = pitch
+ )
+ )
},
valueRange = 0.5f..2f,
track = { sliderState ->
@@ -125,7 +131,12 @@ fun SpeedCard(
value = pitch,
onValueChange = {
pitch = it
- viewModel.setPlaybackSpeed(speed, pitch)
+ viewModel.handlePlayerActions(
+ PlayerActions.ApplyPlaybackSpeed(
+ speed = speed,
+ pitch = pitch
+ )
+ )
},
valueRange = 0.5f..2f,
track = { sliderState ->
@@ -153,7 +164,12 @@ fun SpeedCard(
onValueChange = {
speed = it
pitch = it
- viewModel.setPlaybackSpeed(speed, pitch)
+ viewModel.handlePlayerActions(
+ PlayerActions.ApplyPlaybackSpeed(
+ speed = speed,
+ pitch = pitch
+ )
+ )
},
valueRange = 0.5f..2f,
track = { sliderState ->
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/screens/settings/SettingsScreen.kt b/app/src/main/java/com/sosauce/cutemusic/ui/screens/settings/SettingsScreen.kt
index 394e82f..bcda213 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/screens/settings/SettingsScreen.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/screens/settings/SettingsScreen.kt
@@ -16,6 +16,7 @@ import com.sosauce.cutemusic.ui.navigation.Screen
import com.sosauce.cutemusic.ui.screens.settings.compenents.AboutCard
import com.sosauce.cutemusic.ui.screens.settings.compenents.Misc
import com.sosauce.cutemusic.ui.screens.settings.compenents.ThemeManagement
+import com.sosauce.cutemusic.ui.screens.settings.compenents.UISettings
import com.sosauce.cutemusic.ui.shared_components.AppBar
@@ -49,7 +50,7 @@ fun SettingsScreen(
) {
AboutCard()
ThemeManagement()
- //UISettings()
+ UISettings()
Misc(onNavigateTo = onNavigate)
}
}
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 f61f909..9328df8 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
@@ -75,10 +75,10 @@ inline fun SettingsCards(
}
}
}
- Switch(
- checked = checked,
- onCheckedChange = { onCheckedChange() }
- )
+ Switch(
+ checked = checked,
+ onCheckedChange = { onCheckedChange() }
+ )
}
}
}
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 386b46d..494f998 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
@@ -17,16 +17,16 @@ 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.compose.ui.unit.sp
import com.sosauce.cutemusic.R
import com.sosauce.cutemusic.data.datastore.rememberFollowSys
import com.sosauce.cutemusic.data.datastore.rememberUseAmoledMode
import com.sosauce.cutemusic.data.datastore.rememberUseArtTheme
+import com.sosauce.cutemusic.data.datastore.rememberUseClassicSlider
import com.sosauce.cutemusic.data.datastore.rememberUseDarkMode
import com.sosauce.cutemusic.data.datastore.rememberUseSystemFont
-import com.sosauce.cutemusic.ui.customs.restart
import com.sosauce.cutemusic.ui.navigation.Screen
import com.sosauce.cutemusic.ui.shared_components.CuteText
+import com.sosauce.cutemusic.utils.restart
@Composable
fun Misc(
@@ -131,6 +131,7 @@ fun ThemeManagement() {
@Composable
fun UISettings() {
var useArtTheme by rememberUseArtTheme()
+ var useClassicSlider by rememberUseClassicSlider()
Column {
CuteText(
@@ -138,20 +139,27 @@ fun UISettings() {
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(horizontal = 34.dp, vertical = 8.dp)
)
+// SettingsCards(
+// checked = useArtTheme,
+// onCheckedChange = { useArtTheme = !useArtTheme },
+// topDp = 24.dp,
+// bottomDp = 24.dp,
+// text = stringResource(id = R.string.use_art),
+// optionalDescription = {
+// CuteText(
+// text = "App's theme will follow the currently playing music's art",
+// color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.85f),
+// fontSize = 13.sp
+//
+// )
+// }
+// )
SettingsCards(
- checked = useArtTheme,
- onCheckedChange = { useArtTheme = !useArtTheme },
+ checked = useClassicSlider,
+ onCheckedChange = { useClassicSlider = !useClassicSlider },
topDp = 24.dp,
bottomDp = 24.dp,
- text = stringResource(id = R.string.use_art),
- optionalDescription = {
- CuteText(
- text = "App's theme will follow the currently playing music's art",
- color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.85f),
- fontSize = 13.sp
-
- )
- }
+ text = stringResource(id = R.string.classic_slider),
)
}
}
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
new file mode 100644
index 0000000..62c4ff4
--- /dev/null
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/shared_components/MusicDetailsDialog.kt
@@ -0,0 +1,201 @@
+package com.sosauce.cutemusic.ui.shared_components
+
+import android.net.Uri
+import androidx.compose.foundation.basicMarquee
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import androidx.media3.common.MediaItem
+import coil3.compose.AsyncImage
+import com.sosauce.cutemusic.R
+import com.sosauce.cutemusic.data.MusicState
+import com.sosauce.cutemusic.utils.ImageUtils
+import com.sosauce.cutemusic.utils.formatBinarySize
+import com.sosauce.cutemusic.utils.getBitrate
+
+@Composable
+fun MusicDetailsDialog(
+ music: MediaItem,
+ onDismissRequest: () -> Unit
+) {
+ val context = LocalContext.current
+ val uri = remember { Uri.parse(music.mediaMetadata.extras?.getString("uri")) }
+ val fileBitrate = uri.getBitrate(context)
+ val fileType =
+ context.contentResolver.getType(uri)
+
+ AlertDialog(
+ onDismissRequest = onDismissRequest,
+ confirmButton = {
+ TextButton(
+ onClick = onDismissRequest
+ ) {
+ CuteText(stringResource(R.string.okay))
+ }
+ },
+ title = {
+ CuteText(stringResource(R.string.details))
+ },
+ text = {
+ Column {
+ Card(
+ modifier = Modifier
+ .fillMaxWidth(),
+ shape = RoundedCornerShape(24.dp),
+ colors = CardDefaults.cardColors(
+ MaterialTheme.colorScheme.surfaceContainerHighest
+ )
+ ) {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ AsyncImage(
+ model = ImageUtils.imageRequester(
+ img = music.mediaMetadata.artworkUri,
+ context = context
+ ),
+ contentDescription = null,
+ modifier = Modifier
+ .size(100.dp)
+ .padding(15.dp)
+ .clip(RoundedCornerShape(15)),
+ contentScale = ContentScale.Crop
+
+ )
+ Column {
+ CuteText(
+ text = music.mediaMetadata.title.toString(),
+ modifier = Modifier
+ .basicMarquee()
+ )
+ CuteText(
+ text = music.mediaMetadata.artist.toString(),
+ color = MaterialTheme.colorScheme.onBackground.copy(0.85f),
+ modifier = Modifier.basicMarquee()
+ )
+ }
+ }
+ }
+ Spacer(Modifier.height(10.dp))
+ CuteText(
+ text = "${stringResource(id = R.string.size)}: ${
+ music.mediaMetadata.extras?.getLong(
+ "size"
+ )?.formatBinarySize()
+ }",
+ modifier = Modifier.padding(bottom = 5.dp)
+ )
+ CuteText(
+ text = "${stringResource(id = R.string.bitrate)}: $fileBitrate",
+
+ modifier = Modifier.padding(bottom = 5.dp)
+ )
+ CuteText(
+ text = "${stringResource(id = R.string.type)}: $fileType",
+
+ modifier = Modifier.padding(bottom = 5.dp)
+ )
+ }
+ }
+ )
+}
+
+@Composable
+fun MusicStateDetailsDialog(
+ musicState: MusicState,
+ onDismissRequest: () -> Unit
+) {
+ val context = LocalContext.current
+ val uri = remember { Uri.parse(musicState.currentMusicUri) }
+ val fileBitrate = uri.getBitrate(context)
+ val fileType =
+ context.contentResolver.getType(uri)
+
+ AlertDialog(
+ onDismissRequest = onDismissRequest,
+ confirmButton = {
+ TextButton(
+ onClick = onDismissRequest
+ ) {
+ CuteText(stringResource(R.string.okay))
+ }
+ },
+ title = {
+ CuteText(stringResource(R.string.details))
+ },
+ text = {
+ Column {
+ Card(
+ modifier = Modifier
+ .fillMaxWidth(),
+ shape = RoundedCornerShape(24.dp),
+ colors = CardDefaults.cardColors(
+ MaterialTheme.colorScheme.surfaceContainerHighest
+ )
+ ) {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ AsyncImage(
+ model = ImageUtils.imageRequester(
+ img = musicState.currentArt,
+ context = context
+ ),
+ contentDescription = null,
+ modifier = Modifier
+ .size(100.dp)
+ .padding(15.dp)
+ .clip(RoundedCornerShape(15)),
+ contentScale = ContentScale.Crop
+
+ )
+ Column {
+ CuteText(
+ text = musicState.currentlyPlaying,
+ modifier = Modifier
+ .basicMarquee()
+ )
+ CuteText(
+ text = musicState.currentArtist,
+ color = MaterialTheme.colorScheme.onBackground.copy(0.85f),
+ modifier = Modifier.basicMarquee()
+ )
+ }
+ }
+ }
+ Spacer(Modifier.height(10.dp))
+ CuteText(
+ text = "${stringResource(id = R.string.size)}: ${
+ musicState.currentSize.formatBinarySize()
+ }",
+ modifier = Modifier.padding(bottom = 5.dp)
+ )
+ CuteText(
+ text = "${stringResource(id = R.string.bitrate)}: $fileBitrate",
+
+ modifier = Modifier.padding(bottom = 5.dp)
+ )
+ CuteText(
+ text = "${stringResource(id = R.string.type)}: $fileType",
+
+ modifier = Modifier.padding(bottom = 5.dp)
+ )
+ }
+ }
+ )
+}
\ No newline at end of file
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 c76f71f..adc6179 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
@@ -1,76 +1,107 @@
package com.sosauce.cutemusic.ui.shared_components
import android.app.Application
+import android.content.ComponentName
import android.net.Uri
-import android.os.Handler
-import android.os.Looper
-import android.util.Log
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
-import androidx.media3.common.PlaybackParameters
import androidx.media3.common.Player
import androidx.media3.session.MediaController
-import com.google.common.util.concurrent.ListenableFuture
+import androidx.media3.session.SessionToken
import com.google.common.util.concurrent.MoreExecutors
+import com.sosauce.cutemusic.data.MusicState
import com.sosauce.cutemusic.data.actions.PlayerActions
-import com.sosauce.cutemusic.data.datastore.rememberKillService
import com.sosauce.cutemusic.domain.model.Lyrics
-import com.sosauce.cutemusic.ui.customs.playAtIndex
+import com.sosauce.cutemusic.domain.repository.MediaStoreHelper
+import com.sosauce.cutemusic.main.PlaybackService
+import com.sosauce.cutemusic.utils.applyLoop
+import com.sosauce.cutemusic.utils.applyPlaybackSpeed
+import com.sosauce.cutemusic.utils.applyShuffle
+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.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import org.jaudiotagger.audio.AudioFileIO
import org.jaudiotagger.tag.FieldKey
import java.io.File
-class MusicViewModel (
- private val controllerFuture: ListenableFuture,
- application: Application
+class MusicViewModel(
+ application: Application,
+ private val mediaStoreHelper: MediaStoreHelper
) : AndroidViewModel(application) {
private var mediaController: MediaController? by mutableStateOf(null)
- private val shouldKill by rememberKillService(application)
-
-
var selectedItem by mutableIntStateOf(0)
+ private val _musicState = MutableStateFlow(MusicState())
+ val musicState = _musicState.asStateFlow()
- var currentlyPlaying by mutableStateOf("")
- var currentArtist by mutableStateOf("")
- var currentArt: Uri? by mutableStateOf(null)
- var isCurrentlyPlaying by mutableStateOf(false)
- var currentPosition by mutableLongStateOf(0L)
- var currentMusicDuration by mutableLongStateOf(0L)
- var currentMusicUri by mutableStateOf("")
- var currentLyrics by mutableStateOf(listOf())
- var isLooping by mutableStateOf(false)
- var isShuffling by mutableStateOf(false)
- var currentPath by mutableStateOf("")
-
- private var currentLrcFile by mutableStateOf(null)
-
private val playerListener = object : Player.Listener {
override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) {
super.onMediaMetadataChanged(mediaMetadata)
- currentlyPlaying = (mediaMetadata.title ?: currentlyPlaying).toString()
- currentArtist = (mediaMetadata.artist ?: currentArtist).toString()
- currentArt = mediaMetadata.artworkUri ?: currentArt
- currentPath = (mediaMetadata.extras?.getString("path") ?: currentPath)
- currentMusicUri = mediaMetadata.extras?.getString("uri") ?: currentMusicUri
- currentLrcFile = loadLrcFile(currentPath)
- currentLyrics = parseLrcFile(currentLrcFile)
+ _musicState.value = _musicState.value.copy(
+ currentlyPlaying = mediaMetadata.title.toString(),
+ 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!",
+ currentLrcFile = loadLrcFile(musicState.value.currentPath),
+ currentLyrics = parseLrcFile(musicState.value.currentLrcFile),
+ currentAlbum = mediaMetadata.albumTitle.toString(),
+ currentAlbumId = mediaMetadata.extras?.getLong("album_id") ?: 0,
+ currentSize = mediaMetadata.extras?.getLong("size") ?: 0
+ )
}
+
override fun onIsPlayingChanged(isPlaying: Boolean) {
super.onIsPlayingChanged(isPlaying)
- isCurrentlyPlaying = isPlaying
+ _musicState.value = _musicState.value.copy(
+ isCurrentlyPlaying = isPlaying
+ )
+ }
+
+ override fun onRepeatModeChanged(repeatMode: Int) {
+ super.onRepeatModeChanged(repeatMode)
+ when (repeatMode) {
+ Player.REPEAT_MODE_ONE -> {
+ _musicState.value = _musicState.value.copy(
+ isLooping = true
+ )
+ }
+
+ Player.REPEAT_MODE_OFF -> {
+ _musicState.value = _musicState.value.copy(
+ isLooping = false
+ )
+ }
+
+
+ else -> {
+ _musicState.value = _musicState.value.copy(
+ isLooping = false
+ )
+ }
+ }
+ }
+
+ override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
+ super.onShuffleModeEnabledChanged(shuffleModeEnabled)
+ _musicState.value = _musicState.value.copy(
+ isShuffling = shuffleModeEnabled
+ )
}
@@ -78,31 +109,40 @@ class MusicViewModel (
super.onEvents(player, events)
viewModelScope.launch {
while (player.isPlaying) {
- currentMusicDuration = player.duration
- currentPosition = player.currentPosition
- delay(550)
+ _musicState.value = _musicState.value.copy(
+ currentMusicDuration = player.duration,
+ currentPosition = player.currentPosition
+ )
+ delay(500)
}
}
}
}
- companion object {
- const val CUTE_ERROR = "CuteError"
- }
-
init {
- controllerFuture.addListener(
- {
- Handler(Looper.getMainLooper()).post {
- mediaController = controllerFuture.get()
- mediaController!!.addListener(playerListener)
- }
- },
- MoreExecutors.directExecutor()
- )
+ MediaController
+ .Builder(
+ application,
+ SessionToken(
+ application,
+ ComponentName(application, PlaybackService::class.java)
+ )
+ )
+ .buildAsync()
+ .apply {
+ addListener(
+ {
+ mediaController = get()
+ mediaController!!.addListener(playerListener)
+ mediaController!!.setMediaItems(mediaStoreHelper.musics)
+ },
+ MoreExecutors.directExecutor()
+ )
+ }
}
+
private fun loadLrcFile(path: String): File? {
val lrcFilePath = path.replaceAfterLast('.', "lrc")
val lrcFile = File(lrcFilePath)
@@ -111,6 +151,8 @@ class MusicViewModel (
private fun parseLrcFile(file: File?): List {
val lyrics = mutableListOf()
+ val regex = Regex("""\[(\d{2}):(\d{2})\.(\d{2})]""")
+
if (file == null) {
return emptyList()
}
@@ -118,9 +160,7 @@ class MusicViewModel (
viewModelScope.launch {
file.bufferedReader().useLines { lines ->
lines.forEach { line ->
- val regex = Regex("""\[(\d{2}):(\d{2})\.(\d{2})]""")
val matchResult = regex.find(line)
-
if (matchResult != null) {
val (minutes, seconds, hundredths) = matchResult.destructured
val timeInMillis =
@@ -155,77 +195,23 @@ class MusicViewModel (
}
}
}
+
override fun onCleared() {
super.onCleared()
mediaController!!.removeListener(playerListener)
mediaController!!.release()
}
-
-
-
fun getPlaybackSpeed() = mediaController!!.playbackParameters
- private fun playAtIndex(mediaId: String) {
- try {
- mediaController!!.playAtIndex(
- mediaId = mediaId
- )
- } catch (e: Exception) {
- Log.d(CUTE_ERROR, e.message.toString())
- }
- }
-
- fun itemClicked(
- mediaId: String,
- musics1: List
- ) {
-
- if (mediaController!!.mediaItemCount == 0) {
- musics1.forEach {
- mediaController!!.addMediaItem(it)
+ fun isPlayerReady(): Boolean {
+ return if (mediaController == null) false else
+ when (mediaController!!.playbackState) {
+ Player.STATE_IDLE -> false
+ Player.STATE_READY -> true
+ else -> true
}
- mediaController!!.prepare()
- }
-
- playAtIndex(mediaId)
-
- }
-
-
- fun isPlaylistEmptyAndDataNotNull(): Boolean {
- return if (mediaController == null) false else mediaController!!.mediaItemCount != 0
- }
-
-
- fun setPlaybackSpeed(
- speed: Float = 1f,
- pitch: Float = 1f
- ) {
- mediaController!!.playbackParameters = PlaybackParameters(
- speed,
- pitch
- )
- }
-
-
-
- fun setLoop(
- shouldLoop: Boolean
- ) {
- if (shouldLoop) {
- mediaController!!.repeatMode = Player.REPEAT_MODE_ONE
- isLooping = true
- } else {
- mediaController!!.repeatMode = Player.REPEAT_MODE_OFF
- isLooping = false
- }
- }
-
- fun setShuffle(shouldShuffle: Boolean) {
- mediaController!!.shuffleModeEnabled = shouldShuffle
- isShuffling = mediaController!!.shuffleModeEnabled
}
fun quickPlay(uri: Uri?) {
@@ -242,14 +228,42 @@ class MusicViewModel (
fun handlePlayerActions(action: PlayerActions) {
when (action) {
+ is PlayerActions.RestartSong -> mediaController!!.seekTo(0)
+ is PlayerActions.PlayRandom -> mediaController!!.playRandom()
+ is PlayerActions.ApplyLoop -> mediaController!!.applyLoop()
+ is PlayerActions.ApplyShuffle -> mediaController!!.applyShuffle()
is PlayerActions.PlayOrPause -> if (mediaController!!.isPlaying) mediaController!!.pause() else mediaController!!.play()
is PlayerActions.SeekToNextMusic -> mediaController!!.seekToNextMediaItem()
is PlayerActions.SeekToPreviousMusic -> mediaController!!.seekToPreviousMediaItem()
is PlayerActions.SeekTo -> mediaController!!.seekTo(mediaController!!.currentPosition + action.position)
is PlayerActions.SeekToSlider -> mediaController!!.seekTo(action.position)
is PlayerActions.RewindTo -> mediaController!!.seekTo(mediaController!!.currentPosition - action.position)
- is PlayerActions.RestartSong -> mediaController!!.seekTo(0)
+ is PlayerActions.ApplyPlaybackSpeed -> mediaController!!.applyPlaybackSpeed(
+ action.speed,
+ action.pitch
+ )
+
+ is PlayerActions.StartAlbumPlayback -> mediaController!!.playFromAlbum(
+ action.albumName,
+ action.mediaId,
+ mediaStoreHelper.musics
+ )
+
+ is PlayerActions.StartArtistPlayback -> mediaController!!.playFromArtist(
+ action.artistName,
+ action.mediaId,
+ mediaStoreHelper.musics
+ )
+
+ is PlayerActions.StartPlayback -> {
+ // If a user started album/artist playback, we need to make sure all songs are re-fed to Exoplayer when they want to play from anywhere else
+ if (mediaController!!.mediaItemCount != mediaStoreHelper.musics.size) {
+ mediaController!!.setMediaItems(mediaStoreHelper.musics)
+ mediaController!!.playAtIndex(action.mediaId)
+ } else {
+ mediaController!!.playAtIndex(action.mediaId)
+ }
+ }
}
}
}
-
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 65daeea..9bac6f2 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
@@ -26,23 +26,23 @@ class PostViewModel(
) : AndroidViewModel(application) {
var musics by mutableStateOf(
- mediaStoreHelper.getMusics()
+ mediaStoreHelper.musics
)
var albums by mutableStateOf(
- mediaStoreHelper.getAlbums()
+ mediaStoreHelper.albums
)
var artists by mutableStateOf(
- mediaStoreHelper.getArtists()
+ mediaStoreHelper.artists
)
var folders by mutableStateOf(
- mediaStoreHelper.getFoldersWithMusics()
+ mediaStoreHelper.fetchFoldersWithMusics()
)
private val observer = MediaStoreObserver {
- musics = mediaStoreHelper.getMusics()
+ musics = mediaStoreHelper.fetchMusics()
}
@@ -121,19 +121,21 @@ class PostViewModel(
listToHandle: ListToHandle,
sortingType: SortingType,
) {
- when(listToHandle) {
+ when (listToHandle) {
ListToHandle.TRACKS -> {
musics = if (sortingType == SortingType.ASCENDING)
musics.sortedBy { it.mediaMetadata.title.toString() }
else
musics.sortedByDescending { it.mediaMetadata.title.toString() }
}
+
ListToHandle.ALBUMS -> {
albums = if (sortingType == SortingType.ASCENDING)
albums.sortedBy { it.name }
else
albums.sortedByDescending { it.name }
}
+
ListToHandle.ARTISTS -> {
artists = if (sortingType == SortingType.ASCENDING)
artists.sortedBy { it.name }
@@ -147,25 +149,27 @@ class PostViewModel(
listToHandle: ListToHandle,
query: String = ""
) {
- when(listToHandle) {
+ when (listToHandle) {
ListToHandle.TRACKS -> {
- musics = mediaStoreHelper.getMusics().filter {
+ musics = mediaStoreHelper.musics.filter {
it.mediaMetadata.title?.contains(
other = query,
ignoreCase = true
) == true
}
}
+
ListToHandle.ALBUMS -> {
- albums = mediaStoreHelper.getAlbums().filter {
+ albums = mediaStoreHelper.albums.filter {
it.name.contains(
other = query,
ignoreCase = true
)
}
}
+
ListToHandle.ARTISTS -> {
- artists = mediaStoreHelper.getArtists().filter {
+ artists = mediaStoreHelper.artists.filter {
it.name.contains(
other = query,
ignoreCase = true
@@ -174,5 +178,5 @@ class PostViewModel(
}
}
}
- }
+}
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 841328e..efb175d 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
@@ -2,14 +2,11 @@
package com.sosauce.cutemusic.ui.shared_components
-import android.graphics.drawable.Animatable2
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionScope
import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.animateDpAsState
-import androidx.compose.animation.core.animateIntAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.slideInVertically
@@ -36,7 +33,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
@@ -54,7 +50,7 @@ import kotlinx.coroutines.launch
@Composable
fun SharedTransitionScope.CuteSearchbar(
modifier: Modifier = Modifier,
- query: String,
+ query: String = "",
onQueryChange: (String) -> Unit = {},
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
@@ -63,38 +59,9 @@ fun SharedTransitionScope.CuteSearchbar(
onHandlePlayerActions: (PlayerActions) -> Unit,
isPlaying: Boolean,
animatedVisibilityScope: AnimatedVisibilityScope,
- isPlaylistEmpty: Boolean,
- onNavigate: () -> Unit
-) = CustomSearchbar(
- query = query,
- onQueryChange = onQueryChange,
- leadingIcon = leadingIcon,
- trailingIcon = trailingIcon,
- placeholder = placeholder,
- modifier = modifier,
- currentlyPlaying = currentlyPlaying,
- onHandlePlayerActions = onHandlePlayerActions,
- isPlaying = isPlaying,
- animatedVisibilityScope = animatedVisibilityScope,
- isPlaylistEmpty = isPlaylistEmpty,
- onNavigate = onNavigate
-)
-
-
-@Composable
-private fun SharedTransitionScope.CustomSearchbar(
- query: String,
- onQueryChange: (String) -> Unit,
- leadingIcon: @Composable (() -> Unit)? = null,
- trailingIcon: @Composable (() -> Unit)? = null,
- placeholder: @Composable (() -> Unit)? = null,
- modifier: Modifier,
- currentlyPlaying: String,
- onHandlePlayerActions: (PlayerActions) -> Unit,
- isPlaying: Boolean,
- animatedVisibilityScope: AnimatedVisibilityScope,
- isPlaylistEmpty: Boolean,
- onNavigate: () -> Unit
+ isPlayerReady: Boolean,
+ onNavigate: () -> Unit = {},
+ showSearchField: Boolean = true,
) {
val focusManager = LocalFocusManager.current
val roundedShape = 24.dp
@@ -112,21 +79,24 @@ private fun SharedTransitionScope.CustomSearchbar(
shape = RoundedCornerShape(roundedShape)
)
.thenIf(
- isPlaylistEmpty,
+ isPlayerReady,
Modifier.clickable {
onNavigate()
}
)
) {
AnimatedVisibility(
- visible = isPlaylistEmpty,
- enter = fadeIn() + slideInVertically(initialOffsetY = { it })
+ visible = isPlayerReady,
+ enter = fadeIn() + slideInVertically(
+ animationSpec = tween(500),
+ initialOffsetY = { it }
+ )
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(start = 15.dp)
- .fillMaxWidth()
+ .fillMaxWidth(),
) {
Row(
modifier = Modifier.weight(1f)
@@ -139,7 +109,7 @@ private fun SharedTransitionScope.CustomSearchbar(
state = rememberSharedContentState(key = "arrow"),
animatedVisibilityScope = animatedVisibilityScope,
boundsTransform = { _, _ ->
- tween(durationMillis = 500)
+ tween(500)
}
)
)
@@ -151,7 +121,7 @@ private fun SharedTransitionScope.CustomSearchbar(
state = rememberSharedContentState(key = "currentlyPlaying"),
animatedVisibilityScope = animatedVisibilityScope,
boundsTransform = { _, _ ->
- tween(durationMillis = 500)
+ tween(500)
}
)
.basicMarquee()
@@ -188,7 +158,7 @@ private fun SharedTransitionScope.CustomSearchbar(
state = rememberSharedContentState(key = "skipPreviousButton"),
animatedVisibilityScope = animatedVisibilityScope,
boundsTransform = { _, _ ->
- tween(durationMillis = 500)
+ tween(500)
}
)
)
@@ -203,7 +173,7 @@ private fun SharedTransitionScope.CustomSearchbar(
state = rememberSharedContentState(key = "playPauseIcon"),
animatedVisibilityScope = animatedVisibilityScope,
boundsTransform = { _, _ ->
- tween(durationMillis = 500)
+ tween(500)
}
)
)
@@ -234,41 +204,43 @@ private fun SharedTransitionScope.CustomSearchbar(
)
}
.sharedElement(
- state = rememberSharedContentState(key = "skipNextButton"),
- animatedVisibilityScope = animatedVisibilityScope,
- boundsTransform = { _, _ ->
- tween(durationMillis = 500)
- }
- )
+ state = rememberSharedContentState(key = "skipNextButton"),
+ animatedVisibilityScope = animatedVisibilityScope,
+ boundsTransform = { _, _ ->
+ tween(500)
+ }
+ )
)
}
}
}
}
- TextField(
- value = query,
- onValueChange = onQueryChange,
- colors = TextFieldDefaults.colors(
- unfocusedContainerColor = MaterialTheme.colorScheme.secondaryContainer.copy(0.5f),
- focusedContainerColor = MaterialTheme.colorScheme.secondaryContainer.copy(0.5f),
- disabledIndicatorColor = Color.Transparent,
- focusedIndicatorColor = Color.Transparent,
- unfocusedIndicatorColor = Color.Transparent,
- errorIndicatorColor = Color.Transparent
- ),
- shape = RoundedCornerShape(50.dp),
- leadingIcon = leadingIcon,
- trailingIcon = trailingIcon,
- placeholder = placeholder,
- singleLine = true,
- modifier = Modifier
- .fillMaxWidth()
- .padding(6.dp),
- keyboardActions = KeyboardActions(
- onDone = { focusManager.clearFocus() }
- )
+ if (showSearchField) {
+ TextField(
+ value = query,
+ onValueChange = onQueryChange,
+ colors = TextFieldDefaults.colors(
+ unfocusedContainerColor = MaterialTheme.colorScheme.secondaryContainer.copy(0.5f),
+ focusedContainerColor = MaterialTheme.colorScheme.secondaryContainer.copy(0.5f),
+ disabledIndicatorColor = Color.Transparent,
+ focusedIndicatorColor = Color.Transparent,
+ unfocusedIndicatorColor = Color.Transparent,
+ errorIndicatorColor = Color.Transparent
+ ),
+ shape = RoundedCornerShape(50.dp),
+ leadingIcon = leadingIcon,
+ trailingIcon = trailingIcon,
+ placeholder = placeholder,
+ singleLine = true,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(6.dp),
+ keyboardActions = KeyboardActions(
+ onDone = { focusManager.clearFocus() }
+ )
- )
+ )
+ }
}
}
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/theme/Color.kt b/app/src/main/java/com/sosauce/cutemusic/ui/theme/Color.kt
index 2644850..27ef94f 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/theme/Color.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/theme/Color.kt
@@ -58,10 +58,6 @@ val md_theme_dark_outline = Color(0xFF9C8D93)
val md_theme_dark_inverseOnSurface = Color(0xFF1F1A1C)
val md_theme_dark_inverseSurface = Color(0xFFEBE0E2)
val md_theme_dark_inversePrimary = Color(0xFFB0137F)
-val md_theme_dark_shadow = Color(0xFF000000)
val md_theme_dark_surfaceTint = Color(0xFFFFAFD7)
val md_theme_dark_outlineVariant = Color(0xFF504349)
-val md_theme_dark_scrim = Color(0xFF000000)
-
-
-val seed = Color(0xFFF555B8)
\ No newline at end of file
+val md_theme_dark_scrim = Color(0xFF000000)
\ No newline at end of file
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/theme/ColorEngineViewModel.kt b/app/src/main/java/com/sosauce/cutemusic/ui/theme/ColorEngineViewModel.kt
index 92937f5..7ab3d03 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/theme/ColorEngineViewModel.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/theme/ColorEngineViewModel.kt
@@ -18,7 +18,7 @@ class ColorEngineViewModel : ViewModel() {
fun generateNewPalette(
uri: Uri,
context: Context
- ) : ImageBitmap?{
+ ): ImageBitmap? {
var imageBitmap by mutableStateOf(null)
viewModelScope.launch(Dispatchers.IO) {
diff --git a/app/src/main/java/com/sosauce/cutemusic/ui/theme/Theme.kt b/app/src/main/java/com/sosauce/cutemusic/ui/theme/Theme.kt
index e6be16c..757ea1a 100644
--- a/app/src/main/java/com/sosauce/cutemusic/ui/theme/Theme.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/ui/theme/Theme.kt
@@ -1,5 +1,3 @@
-@file:Suppress("PrivatePropertyName")
-
package com.sosauce.cutemusic.ui.theme
import android.os.Build
diff --git a/app/src/main/java/com/sosauce/cutemusic/utils/Customs.kt b/app/src/main/java/com/sosauce/cutemusic/utils/Customs.kt
index a5fd840..8710c9b 100644
--- a/app/src/main/java/com/sosauce/cutemusic/utils/Customs.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/utils/Customs.kt
@@ -5,9 +5,6 @@ import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.icons.Icons
-import androidx.compose.material3.IconButtonColors
-import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
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 1f93ae8..7c244b7 100644
--- a/app/src/main/java/com/sosauce/cutemusic/utils/Extensions.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/utils/Extensions.kt
@@ -1,12 +1,20 @@
package com.sosauce.cutemusic.utils
+import android.content.Context
+import android.content.Intent
+import android.media.MediaMetadataRetriever
+import android.net.Uri
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import androidx.media3.common.MediaItem
+import androidx.media3.common.PlaybackParameters
+import androidx.media3.common.Player
import com.sosauce.cutemusic.data.datastore.rememberIsLandscape
+import java.util.Locale
fun Modifier.thenIf(
condition: Boolean,
@@ -19,11 +27,132 @@ fun Modifier.thenIf(
)
}
-// How bad of a programmer am I for all the below functions
+fun Long.formatBinarySize(): String {
+ val kiloByteAsByte = 1.0 * 1024.0
+ val megaByteAsByte = 1.0 * 1024.0 * 1024.0
+ val gigaByteAsByte = 1.0 * 1024.0 * 1024.0 * 1024.0
+ return when {
+ this < kiloByteAsByte -> "${this.toDouble()} B"
+ this >= kiloByteAsByte && this < megaByteAsByte -> "${
+ String.format(
+ Locale.getDefault(),
+ "%.2f",
+ (this / kiloByteAsByte)
+ )
+ } KB"
+
+ this >= megaByteAsByte && this < gigaByteAsByte -> "${
+ String.format(
+ Locale.getDefault(),
+ "%.2f",
+ (this / megaByteAsByte)
+ )
+ } MB"
+
+ else -> "Too Big!"
+ }
+}
+
+fun Context.restart() {
+ val intent = packageManager.getLaunchIntentForPackage(packageName)!!
+ val componentName = intent.component!!
+ val restartIntent = Intent.makeRestartActivityTask(componentName)
+ startActivity(restartIntent)
+ Runtime.getRuntime().exit(0)
+}
+
+fun Player.playAtIndex(
+ mediaId: String
+) {
+ val index = (0 until mediaItemCount).indexOfFirst { getMediaItemAt(it).mediaId == mediaId }
+ index.takeIf { it != -1 }?.let {
+ seekTo(it, 0)
+ play()
+ }
+}
+
+fun Player.playRandom() {
+ val range = 0..mediaItemCount
+ seekTo(range.random(), 0)
+ play()
+}
+
+fun Player.playFromAlbum(
+ albumName: String,
+ mediaId: String? = null,
+ musics: List
+) {
+ clearMediaItems()
+ musics.forEach {
+ if (it.mediaMetadata.albumTitle.toString() == albumName) {
+ addMediaItem(it)
+ }
+ }
+
+ if (mediaId == null) {
+ playRandom()
+ } else {
+ playAtIndex(mediaId)
+ }
+}
+
+fun Player.playFromArtist(
+ artistsName: String,
+ mediaId: String? = null,
+ musics: List
+) {
+ clearMediaItems()
+ musics.forEach {
+ if (it.mediaMetadata.artist.toString() == artistsName) {
+ addMediaItem(it)
+ }
+ }
+
+ if (mediaId == null) {
+ playRandom()
+ } else {
+ playAtIndex(mediaId)
+ }
+}
+
+fun Player.applyLoop() {
+ repeatMode = when (repeatMode) {
+ Player.REPEAT_MODE_OFF -> Player.REPEAT_MODE_ONE
+ Player.REPEAT_MODE_ONE -> Player.REPEAT_MODE_OFF
+ else -> Player.REPEAT_MODE_OFF
+ }
+}
+
+fun Player.applyShuffle() {
+ shuffleModeEnabled = !shuffleModeEnabled
+}
+
+fun Player.applyPlaybackSpeed(
+ speed: Float = 1f,
+ pitch: Float = 1f,
+) {
+ playbackParameters = PlaybackParameters(
+ speed,
+ pitch
+ )
+}
+
+fun Uri.getBitrate(context: Context): String {
+ val retriever = MediaMetadataRetriever()
+ return try {
+ retriever.setDataSource(context, this)
+ val bitrate = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE)
+ bitrate?.toInt()?.div(1000)?.toString()?.plus(" kbps") ?: "Unknown"
+ } catch (e: Exception) {
+ "Error parsing bitrate!"
+ } finally {
+ retriever.release()
+ }
+}
@Composable
fun rememberSearchbarAlignment(
-) : Alignment {
+): Alignment {
val isLandscape = rememberIsLandscape()
@@ -38,7 +167,7 @@ fun rememberSearchbarAlignment(
@Composable
fun rememberSearchbarMaxFloatValue(
-) : Float {
+): Float {
val isLandscape = rememberIsLandscape()
@@ -53,7 +182,7 @@ fun rememberSearchbarMaxFloatValue(
@Composable
fun rememberSearchbarRightPadding(
-) : Dp {
+): Dp {
val isLandscape = rememberIsLandscape()
diff --git a/app/src/main/java/com/sosauce/cutemusic/utils/ImageUtils.kt b/app/src/main/java/com/sosauce/cutemusic/utils/ImageUtils.kt
index 270634b..aa8f34b 100644
--- a/app/src/main/java/com/sosauce/cutemusic/utils/ImageUtils.kt
+++ b/app/src/main/java/com/sosauce/cutemusic/utils/ImageUtils.kt
@@ -11,13 +11,15 @@ import java.io.FileNotFoundException
object ImageUtils {
fun imageRequester(img: Any?, context: Context): ImageRequest {
- return ImageRequest.Builder(context)
+ val request = ImageRequest.Builder(context)
.data(img)
.crossfade(true)
.transformations(
RoundedCornersTransformation(15f)
)
.build()
+
+ return request
}
fun getAlbumArt(albumId: Long): Any? {
diff --git a/app/src/main/res/drawable/round_music_note_24.xml b/app/src/main/res/drawable/round_music_note_24.xml
new file mode 100644
index 0000000..c1fd5db
--- /dev/null
+++ b/app/src/main/res/drawable/round_music_note_24.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 5b29d33..800e81f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -58,4 +58,6 @@
Success
Error while saving changes.
Editor
+ Details
+ Use Classic Slider
\ No newline at end of file
diff --git a/app/src/main/res/xml/automotive_app_desc.xml b/app/src/main/res/xml/automotive_app_desc.xml
new file mode 100644
index 0000000..0a6a3c9
--- /dev/null
+++ b/app/src/main/res/xml/automotive_app_desc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/release/generated/baselineProfiles/baseline-prof.txt b/app/src/release/generated/baselineProfiles/baseline-prof.txt
index 32a07ce..a6ea94d 100644
--- a/app/src/release/generated/baselineProfiles/baseline-prof.txt
+++ b/app/src/release/generated/baselineProfiles/baseline-prof.txt
@@ -16552,10 +16552,10 @@ HSPLcom/sosauce/cutemusic/domain/blacklist/BlacklistedDatabase_Impl$1;->onOpen(L
Lcom/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl;
HSPLcom/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl;->()V
HSPLcom/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl;->(Landroid/content/Context;)V
-HSPLcom/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl;->getAlbums()Ljava/util/List;
-HSPLcom/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl;->getArtists()Ljava/util/List;
-HSPLcom/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl;->getFoldersWithMusics()Ljava/util/List;
-HSPLcom/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl;->getMusics()Ljava/util/List;
+HSPLcom/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl;->fetchAlbums()Ljava/util/List;
+HSPLcom/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl;->fetchArtists()Ljava/util/List;
+HSPLcom/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl;->fetchFoldersWithMusics()Ljava/util/List;
+HSPLcom/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl;->fetchMusics()Ljava/util/List;
Lcom/sosauce/cutemusic/main/App;
HSPLcom/sosauce/cutemusic/main/App;->$r8$lambda$xrutaltFOQbSx_O2rxWYEJHPxKQ(Lcom/sosauce/cutemusic/main/App;Lorg/koin/core/KoinApplication;)Lkotlin/Unit;
HSPLcom/sosauce/cutemusic/main/App;->()V
@@ -16878,7 +16878,7 @@ Lcom/sosauce/cutemusic/ui/shared_components/PostViewModel;
HSPLcom/sosauce/cutemusic/ui/shared_components/PostViewModel;->()V
HSPLcom/sosauce/cutemusic/ui/shared_components/PostViewModel;->(Lcom/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl;Lcom/sosauce/cutemusic/domain/blacklist/BlackDao;)V
HSPLcom/sosauce/cutemusic/ui/shared_components/PostViewModel;->access$getMediaStoreHelperImpl$p(Lcom/sosauce/cutemusic/ui/shared_components/PostViewModel;)Lcom/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl;
-HSPLcom/sosauce/cutemusic/ui/shared_components/PostViewModel;->getMusics()Ljava/util/List;
+HSPLcom/sosauce/cutemusic/ui/shared_components/PostViewModel;->fetchMusics()Ljava/util/List;
HSPLcom/sosauce/cutemusic/ui/shared_components/PostViewModel;->getState()Lkotlinx/coroutines/flow/StateFlow;
HSPLcom/sosauce/cutemusic/ui/shared_components/PostViewModel;->setMusics(Ljava/util/List;)V
Lcom/sosauce/cutemusic/ui/shared_components/PostViewModel$1;
diff --git a/app/src/release/generated/baselineProfiles/startup-prof.txt b/app/src/release/generated/baselineProfiles/startup-prof.txt
index 32a07ce..a6ea94d 100644
--- a/app/src/release/generated/baselineProfiles/startup-prof.txt
+++ b/app/src/release/generated/baselineProfiles/startup-prof.txt
@@ -16552,10 +16552,10 @@ HSPLcom/sosauce/cutemusic/domain/blacklist/BlacklistedDatabase_Impl$1;->onOpen(L
Lcom/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl;
HSPLcom/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl;->()V
HSPLcom/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl;->(Landroid/content/Context;)V
-HSPLcom/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl;->getAlbums()Ljava/util/List;
-HSPLcom/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl;->getArtists()Ljava/util/List;
-HSPLcom/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl;->getFoldersWithMusics()Ljava/util/List;
-HSPLcom/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl;->getMusics()Ljava/util/List;
+HSPLcom/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl;->fetchAlbums()Ljava/util/List;
+HSPLcom/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl;->fetchArtists()Ljava/util/List;
+HSPLcom/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl;->fetchFoldersWithMusics()Ljava/util/List;
+HSPLcom/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl;->fetchMusics()Ljava/util/List;
Lcom/sosauce/cutemusic/main/App;
HSPLcom/sosauce/cutemusic/main/App;->$r8$lambda$xrutaltFOQbSx_O2rxWYEJHPxKQ(Lcom/sosauce/cutemusic/main/App;Lorg/koin/core/KoinApplication;)Lkotlin/Unit;
HSPLcom/sosauce/cutemusic/main/App;->()V
@@ -16878,7 +16878,7 @@ Lcom/sosauce/cutemusic/ui/shared_components/PostViewModel;
HSPLcom/sosauce/cutemusic/ui/shared_components/PostViewModel;->()V
HSPLcom/sosauce/cutemusic/ui/shared_components/PostViewModel;->(Lcom/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl;Lcom/sosauce/cutemusic/domain/blacklist/BlackDao;)V
HSPLcom/sosauce/cutemusic/ui/shared_components/PostViewModel;->access$getMediaStoreHelperImpl$p(Lcom/sosauce/cutemusic/ui/shared_components/PostViewModel;)Lcom/sosauce/cutemusic/domain/repository/MediaStoreHelperImpl;
-HSPLcom/sosauce/cutemusic/ui/shared_components/PostViewModel;->getMusics()Ljava/util/List;
+HSPLcom/sosauce/cutemusic/ui/shared_components/PostViewModel;->fetchMusics()Ljava/util/List;
HSPLcom/sosauce/cutemusic/ui/shared_components/PostViewModel;->getState()Lkotlinx/coroutines/flow/StateFlow;
HSPLcom/sosauce/cutemusic/ui/shared_components/PostViewModel;->setMusics(Ljava/util/List;)V
Lcom/sosauce/cutemusic/ui/shared_components/PostViewModel$1;
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 67fa09d..0484d7b 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,5 +1,5 @@
[versions]
-agp = "8.6.1"
+agp = "8.7.1"
jaudiotagger = "3.0.1"
koinAndroid = "4.0.0"
koinAndroidxCompose = "4.0.0"
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 0f8a7a1..856c82a 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Sat Jan 13 19:30:48 CET 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists