diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index 9411831..b82eb7a 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -7,11 +7,11 @@ - + - + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index a2b26f0..82ca760 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,9 +13,8 @@ android { applicationId "com.lighttigerxiv.simple.mp" minSdk 26 targetSdk 33 - versionCode 21 - versionName "BETA-1.7.1" - + versionCode 22 + versionName "BETA-1.8.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary true @@ -102,4 +101,8 @@ dependencies { //Catppuccin Colors implementation 'com.github.lighttigerxiv:catppuccin-kt:1.0.0' + + //Android Auto + implementation 'androidx.car.app:app:1.2.0' + } \ No newline at end of file diff --git a/app/release/SimpleMP-BETA-1.7.1.apk b/app/release/SimpleMP-BETA-1.8.0.apk similarity index 93% rename from app/release/SimpleMP-BETA-1.7.1.apk rename to app/release/SimpleMP-BETA-1.8.0.apk index 22df897..0e558d0 100644 Binary files a/app/release/SimpleMP-BETA-1.7.1.apk and b/app/release/SimpleMP-BETA-1.8.0.apk differ diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json index be41e57..c3a28e5 100644 --- a/app/release/output-metadata.json +++ b/app/release/output-metadata.json @@ -11,8 +11,8 @@ "type": "SINGLE", "filters": [], "attributes": [], - "versionCode": 21, - "versionName": "BETA-1.7.1", + "versionCode": 22, + "versionName": "BETA-1.8.0", "outputFile": "app-release.apk" } ], diff --git a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/AndroidAutoDetector.kt b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/AndroidAutoDetector.kt new file mode 100644 index 0000000..4d7f4a6 --- /dev/null +++ b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/AndroidAutoDetector.kt @@ -0,0 +1,101 @@ +package com.lighttigerxiv.simple.mp.compose + +import android.annotation.SuppressLint +import android.content.AsyncQueryHandler +import android.content.BroadcastReceiver +import android.content.ContentResolver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.database.Cursor +import android.net.Uri +import androidx.core.content.ContextCompat + + +class AndroidAutoDetector(val context: Context) { + + var onDisconnect: () -> Unit = {} + + companion object { + const val TAG = "AutoConnectionDetector" + + // columnName for provider to query on connection status + const val CAR_CONNECTION_STATE = "CarConnectionState" + + // auto app on your phone will send broadcast with this action when connection state changes + const val ACTION_CAR_CONNECTION_UPDATED = "androidx.car.app.connection.action.CAR_CONNECTION_UPDATED" + + // phone is not connected to car + const val CONNECTION_TYPE_NOT_CONNECTED = 0 + + // phone is connected to Automotive OS + const val CONNECTION_TYPE_NATIVE = 1 + + // phone is connected to Android Auto + const val CONNECTION_TYPE_PROJECTION = 2 + + private const val QUERY_TOKEN = 42 + + private const val CAR_CONNECTION_AUTHORITY = "androidx.car.app.connection" + + private val PROJECTION_HOST_URI = Uri.Builder().scheme("content").authority(CAR_CONNECTION_AUTHORITY).build() + } + + private val carConnectionReceiver = CarConnectionBroadcastReceiver() + private val carConnectionQueryHandler = CarConnectionQueryHandler(context.contentResolver) + + + fun registerCarConnectionReceiver() { + + ContextCompat.registerReceiver(context, carConnectionReceiver, IntentFilter(ACTION_CAR_CONNECTION_UPDATED), ContextCompat.RECEIVER_NOT_EXPORTED) + queryForState() + } + + fun unRegisterCarConnectionReceiver() { + context.unregisterReceiver(carConnectionReceiver) + } + + private fun queryForState() { + carConnectionQueryHandler.startQuery( + QUERY_TOKEN, + null, + PROJECTION_HOST_URI, + arrayOf(CAR_CONNECTION_STATE), + null, + null, + null + ) + } + + inner class CarConnectionBroadcastReceiver : BroadcastReceiver() { + // query for connection state every time the receiver receives the broadcast + override fun onReceive(context: Context?, intent: Intent?) { + queryForState() + } + } + + internal class CarConnectionQueryHandler(resolver: ContentResolver?) : AsyncQueryHandler(resolver) { + // notify new queryed connection status when query complete + override fun onQueryComplete(token: Int, cookie: Any?, response: Cursor?) { + if (response == null) { + + return + } + val carConnectionTypeColumn = response.getColumnIndex(CAR_CONNECTION_STATE) + if (carConnectionTypeColumn < 0) { + + return + } + if (!response.moveToNext()) { + + return + } + val connectionState = response.getInt(carConnectionTypeColumn) + if (connectionState == CONNECTION_TYPE_NOT_CONNECTED) { + + } else { + + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/activities/main/ActivityMain.kt b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/activities/main/ActivityMain.kt index b1c9735..0d16e90 100644 --- a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/activities/main/ActivityMain.kt +++ b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/activities/main/ActivityMain.kt @@ -11,6 +11,7 @@ import android.provider.MediaStore import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.car.app.connection.CarConnection import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -55,6 +56,7 @@ import com.lighttigerxiv.simple.mp.compose.screens.main.floating_album.FloatingA import com.lighttigerxiv.simple.mp.compose.screens.main.floating_artist.FloatingArtistScreen import com.lighttigerxiv.simple.mp.compose.screens.main.floating_artist.FloatingArtistScreenVM import com.lighttigerxiv.simple.mp.compose.screens.main.main.MainScreen +import com.lighttigerxiv.simple.mp.compose.screens.main.player.PlayerScreenVM import com.lighttigerxiv.simple.mp.compose.screens.main.playlists.PlaylistsScreenVM import com.lighttigerxiv.simple.mp.compose.screens.main.playlists.playlist.PlaylistScreenVM import com.lighttigerxiv.simple.mp.compose.screens.main.playlists.playlist.add_songs.AddSongsScreen @@ -82,8 +84,6 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - - if (!getSharedPreferences(packageName, MODE_PRIVATE).getBoolean("setupCompleted", false)) { startActivity( @@ -93,12 +93,39 @@ class MainActivity : ComponentActivity() { finish() } + createNotificationChannels() val vm = ViewModelProvider(this)[MainVM::class.java] val settingsVM = ViewModelProvider(this)[SettingsVM::class.java] + fun onAndroidAutoStateUpdate(connectionState: Int){ + + val carPlayerEnabled = settingsVM.carPlayerSetting.value + val showCarPlayer = vm.showCarPlayer.value + + when(connectionState){ + CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> { + + if(carPlayerEnabled && !showCarPlayer){ + vm.updateShowCarPlayer(false) + } + } + CarConnection.CONNECTION_TYPE_NATIVE ->{ + if(carPlayerEnabled && !showCarPlayer){ + vm.updateShowCarPlayer(true) + } + } + CarConnection.CONNECTION_TYPE_PROJECTION ->{ + if(carPlayerEnabled && !showCarPlayer){ + vm.updateShowCarPlayer(true) + } + } + } + } + + CarConnection(application).type.observe(this, ::onAndroidAutoStateUpdate) setContent { @@ -200,6 +227,7 @@ class MainActivity : ComponentActivity() { MainScreen( mainVM = vm, + settingsVM = settingsVM, activityContext = activityContext, rootNavController = rootNavController, onGetPlaylistImage = { diff --git a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/activities/main/MainVM.kt b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/activities/main/MainVM.kt index 7d60113..1e0cfa7 100644 --- a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/activities/main/MainVM.kt +++ b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/activities/main/MainVM.kt @@ -14,6 +14,7 @@ import android.os.IBinder import android.provider.MediaStore import android.util.Log import android.util.Size +import androidx.car.app.connection.CarConnection import androidx.compose.material.BottomSheetState import androidx.compose.material.BottomSheetValue import androidx.compose.material.ExperimentalMaterialApi @@ -64,6 +65,12 @@ class MainVM(application: Application) : AndroidViewModel(application) { private val _loadingSongs = MutableStateFlow(true) val loadingSongs = _loadingSongs.asStateFlow() + private val _showCarPlayer = MutableStateFlow(false) + val showCarPlayer = _showCarPlayer.asStateFlow() + fun updateShowCarPlayer(newValue: Boolean){ + _showCarPlayer.update { newValue } + } + private val cachedQueries = CacheQueries(getMongoRealm()) @@ -756,6 +763,7 @@ class MainVM(application: Application) : AndroidViewModel(application) { val workManager = WorkManager.getInstance(application) workManager.enqueueUniquePeriodicWork("SyncSongsRequest", ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, syncSongsRequest) + val serviceIntent = Intent(context, SimpleMPService::class.java) context.bindService(serviceIntent, simpleMPConnection, Context.BIND_AUTO_CREATE) } diff --git a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/data/variables/Constants.kt b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/data/variables/Constants.kt index 58fba10..5f82d46 100644 --- a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/data/variables/Constants.kt +++ b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/data/variables/Constants.kt @@ -58,6 +58,8 @@ object Settings{ const val HOME_SORT = "HomeSort" const val ARTISTS_SORT = "ArtistsSort" const val ALBUMS_SORT = "AlbumsSort" + const val CAR_PLAYER = "CarPlayer" + const val KEEP_SCREEN_ON_IN_CAR_MODE = "KeepScreenOnInCarMode" object Values{ object ColorScheme{ @@ -143,6 +145,8 @@ object Settings{ const val OLDEST = "oldest" const val ASCENDENT = "ascendent" const val DESCENDENT = "descendent" + const val ARTIST_ASCENDENT = "artistAscendent" + const val ARTIST_DESCENDENT = "artistDescendent" } } } diff --git a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/data/workers/SyncSongsWorker.kt b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/data/workers/SyncSongsWorker.kt index 91c4d3b..7d713d0 100644 --- a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/data/workers/SyncSongsWorker.kt +++ b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/data/workers/SyncSongsWorker.kt @@ -20,10 +20,18 @@ import kotlinx.coroutines.withContext import java.io.File class SyncSongsWorker(appContext: Context, params: WorkerParameters) : CoroutineWorker(appContext, params) { + + @SuppressLint("Range") override suspend fun doWork(): Result { + return withContext(Dispatchers.IO) { + + val preferences = applicationContext.getSharedPreferences(applicationContext.packageName, Context.MODE_PRIVATE) + val indexingSongs = preferences.getBoolean("indexingSongs", false) - return withContext(Dispatchers.IO){ + if (indexingSongs) return@withContext Result.success() + + preferences.edit().putBoolean("indexingSongs", true).apply() val notificationManager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val notificationBuilder = NotificationCompat.Builder(applicationContext, "Sync") @@ -145,6 +153,8 @@ class SyncSongsWorker(appContext: Context, params: WorkerParameters) : Coroutine notificationManager.cancel(3) + preferences.edit().putBoolean("indexingSongs", false).apply() + return@withContext Result.success() } } diff --git a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/about/AboutScreen.kt b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/about/AboutScreen.kt index b4dd39b..68f7f40 100644 --- a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/about/AboutScreen.kt +++ b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/about/AboutScreen.kt @@ -92,17 +92,11 @@ fun AboutScreen( .background(MaterialTheme.colorScheme.surfaceVariant) .padding(MEDIUM_SPACING), color = MaterialTheme.colorScheme.onSurface, - text = "- Added a smooth animation when opening and closing player \n\n" + - "- Added Latte theme \n\n" + - "- Added Light theme and Dark Theme options \n\n" + - "- Fixed Oled theme on home screen\n\n" + - "- Fixed empty albums and artists showing up due to filter\n\n" + - "- Fixed crash on Android 9 devices\n\n" + - "- Fixed notification on Android 9\n\n" + - "- Fixed songs that don't have artist id or album id showing up" + - "- App now syncs songs in background when opening\n\n" + - "- Added swipe to resync songs on home screen \n\n" + - "- Other small UI Updates " + text = "- Added Experimental Car Player Mode - (It should open when connecting to android auto)\n\n" + + "- Added sort by artist option in Albums\n\n" + + "- Added German Translation (@Integraluminium)\n\n" + + "- Fixed local artist image not showing\n\n" + + "- Fixed other small bugs" ) MediumVerticalSpacer() diff --git a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/albums/AlbumsScreen.kt b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/albums/AlbumsScreen.kt index bd19356..47b4f05 100644 --- a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/albums/AlbumsScreen.kt +++ b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/albums/AlbumsScreen.kt @@ -55,6 +55,9 @@ fun AlbumsScreen( val oldestAlbums = vm.oldestAlbums.collectAsState().value val ascendentAlbums = vm.ascendentAlbums.collectAsState().value val descendentAlbums = vm.descendentAlbums.collectAsState().value + val artistAscendentAlbums = vm.artistAscendentAlbums.collectAsState().value + val artistDescendentAlbums = vm.artistDescendentAlbums.collectAsState().value + val gridCellsCount = when (LocalConfiguration.current.orientation) { Configuration.ORIENTATION_PORTRAIT -> 2 else -> 4 @@ -159,6 +162,36 @@ fun AlbumsScreen( ) } ) + + DropdownMenuItem( + text = { + Text(text = stringResource(id = R.string.SortByArtist)) + }, + onClick = { + + val currentSort = context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE).getString(Settings.ALBUMS_SORT, Settings.Values.Sort.RECENT) + val filterAlgorithm = if (currentSort == Settings.Values.Sort.ARTIST_ASCENDENT) Settings.Values.Sort.ARTIST_DESCENDENT else Settings.Values.Sort.ARTIST_ASCENDENT + val filterAlbums = if(currentSort == Settings.Values.Sort.ARTIST_ASCENDENT) artistDescendentAlbums else artistAscendentAlbums + + vm.updateSortType(filterAlgorithm) + vm.updateCurrentAlbums(filterAlbums) + scope.launch { + vm.updateMenuExpanded(false) + delay(200) + listState.scrollToItem(index = 0) + } + }, + leadingIcon = { + Icon( + modifier = Modifier + .height(20.dp) + .width(20.dp), + painter = painterResource(id = R.drawable.person), + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurface + ) + } + ) } } diff --git a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/albums/AlbumsScreenVM.kt b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/albums/AlbumsScreenVM.kt index ad354d5..6103eea 100644 --- a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/albums/AlbumsScreenVM.kt +++ b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/albums/AlbumsScreenVM.kt @@ -44,7 +44,7 @@ class AlbumsScreenVM(application: Application) : AndroidViewModel(application) { private val _currentAlbums = MutableStateFlow?>(null) val currentAlbums = _currentAlbums.asStateFlow() - fun updateCurrentAlbums(newValue: List?){ + fun updateCurrentAlbums(newValue: List?) { _currentAlbums.update { newValue } } @@ -60,6 +60,12 @@ class AlbumsScreenVM(application: Application) : AndroidViewModel(application) { private val _descendentAlbums = MutableStateFlow?>(null) val descendentAlbums = _descendentAlbums.asStateFlow() + private val _artistAscendentAlbums = MutableStateFlow?>(null) + val artistAscendentAlbums = _artistAscendentAlbums.asStateFlow() + + private val _artistDescendentAlbums = MutableStateFlow?>(null) + val artistDescendentAlbums = _artistDescendentAlbums.asStateFlow() + //************************************************ // Functions @@ -68,11 +74,12 @@ class AlbumsScreenVM(application: Application) : AndroidViewModel(application) { fun loadScreen(mainVM: MainVM) { - fun load(){ + fun load() { val sortType = preferences.getString(Settings.ALBUMS_SORT, Settings.Values.Sort.RECENT) val albums = mainVM.songsData.value?.albums + val artists = mainVM.songsData.value?.artists - if (albums != null) { + if (albums != null && artists != null) { _recentAlbums.update { albums } @@ -82,12 +89,19 @@ class AlbumsScreenVM(application: Application) : AndroidViewModel(application) { _descendentAlbums.update { albums.sortedByDescending { it.title } } + _artistAscendentAlbums.update { albums.sortedBy { it.artistID } } + + _artistDescendentAlbums.update { albums.sortedByDescending { it.artistID } } + + _currentAlbums.update { when (sortType) { Settings.Values.Sort.RECENT -> recentAlbums.value Settings.Values.Sort.OLDEST -> oldestAlbums.value Settings.Values.Sort.ASCENDENT -> ascendentAlbums.value - else -> descendentAlbums.value + Settings.Values.Sort.DESCENDENT -> descendentAlbums.value + Settings.Values.Sort.ARTIST_ASCENDENT -> artistAscendentAlbums.value + else -> artistDescendentAlbums.value } } @@ -97,9 +111,9 @@ class AlbumsScreenVM(application: Application) : AndroidViewModel(application) { load() - viewModelScope.launch{ - withContext(Dispatchers.IO){ - mainVM.songsData.collect{ + viewModelScope.launch { + withContext(Dispatchers.IO) { + mainVM.songsData.collect { load() filterAlbums() } diff --git a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/artist/ArtistScreenVM.kt b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/artist/ArtistScreenVM.kt index 95a4128..1a615ff 100644 --- a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/artist/ArtistScreenVM.kt +++ b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/artist/ArtistScreenVM.kt @@ -91,12 +91,17 @@ class ArtistScreenVM(application: Application) : AndroidViewModel(application) { fun loadScreen(artistID: Long, mainVM: MainVM, settingsVM: SettingsVM) { - fun load(){ + fun load() { val songsData = mainVM.songsData.value val songs = songsData?.songs val artists = songsData?.artists val albums = songsData?.albums + val canDownloadArtistCover = settingsVM.downloadArtistCoverSetting.value + val isInternetAvailable = isNetworkAvailable(context) + val canDownloadOverData = settingsVM.downloadOverDataSetting.value + val isMobileDataEnabled = isOnMobileData(context) + if (songs != null && artists != null && albums != null) { val artist = artists.first { it.id == artistID } @@ -117,7 +122,7 @@ class ArtistScreenVM(application: Application) : AndroidViewModel(application) { val newAlbums = ArrayList() artistSongs.value?.forEach { song -> - if( !newAlbums.any { it.id == song.albumID}){ + if (!newAlbums.any { it.id == song.albumID }) { newAlbums.add(albums.first { it.id == song.albumID }) } } @@ -127,22 +132,18 @@ class ArtistScreenVM(application: Application) : AndroidViewModel(application) { _screenLoaded.update { true } //Loads Artist Image + if (artistQuery!!.alreadyRequested) { if (artistQuery.image != null) { - val imageBytes = Base64.decode(artistQuery.image, Base64.DEFAULT) _tintCover.update { false } _artistCover.update { BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) } } - } else { - val canDownloadArtistCover = settingsVM.downloadArtistCoverSetting.value - val isInternetAvailable = isNetworkAvailable(context) - val canDownloadOverData = settingsVM.downloadOverDataSetting.value - val isMobileDataEnabled = isOnMobileData(context) + } else { if (isInternetAvailable && canDownloadArtistCover) { if ((canDownloadOverData && isMobileDataEnabled) || (!canDownloadOverData && !isMobileDataEnabled)) { @@ -223,8 +224,8 @@ class ArtistScreenVM(application: Application) : AndroidViewModel(application) { load() viewModelScope.launch { - withContext(Dispatchers.IO){ - mainVM.songsData.collect{ + withContext(Dispatchers.IO) { + mainVM.songsData.collect { load() } } diff --git a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/artist/artist_select_cover/SelectArtistCoverScreenVM.kt b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/artist/artist_select_cover/SelectArtistCoverScreenVM.kt index 5d3cb00..844c7ef 100644 --- a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/artist/artist_select_cover/SelectArtistCoverScreenVM.kt +++ b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/artist/artist_select_cover/SelectArtistCoverScreenVM.kt @@ -107,6 +107,8 @@ class SelectArtistCoverScreenVM(application: Application) : AndroidViewModel(app artistsCoversQueries.updateArtistCover(artistID, bitmapString) + artistsCoversQueries.updateArtistAlreadyRequested(artistID) + val artist = artistsCoversQueries.getArtist(artistID) val imageBytes = Base64.decode(artist!!.image, Base64.DEFAULT) diff --git a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/home/HomeScreen.kt b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/home/HomeScreen.kt index 74bbe95..24f4044 100644 --- a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/home/HomeScreen.kt +++ b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/home/HomeScreen.kt @@ -54,7 +54,7 @@ fun HomeScreen( val descendentSongs = vm.descendentSongs.collectAsState().value val syncingSongs = vm.syncingSongs.collectAsState().value - val pullRefresState = rememberPullRefreshState( + val pullRefreshState = rememberPullRefreshState( refreshing = syncingSongs, onRefresh = { vm.reloadSongs(mainVM) @@ -119,7 +119,7 @@ fun HomeScreen( .fillMaxSize() .background(mainVM.surfaceColor.collectAsState().value) .padding(scaffoldPadding) - .pullRefresh(pullRefresState) + .pullRefresh(pullRefreshState) ) { Column { @@ -317,7 +317,7 @@ fun HomeScreen( PullRefreshIndicator( refreshing = syncingSongs, - state = pullRefresState, + state = pullRefreshState, backgroundColor = MaterialTheme.colorScheme.surfaceVariant, contentColor = MaterialTheme.colorScheme.primary, modifier = Modifier.align(Alignment.TopCenter) diff --git a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/home/HomeScreenVM.kt b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/home/HomeScreenVM.kt index 5aefc64..a6d70a5 100644 --- a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/home/HomeScreenVM.kt +++ b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/home/HomeScreenVM.kt @@ -133,17 +133,22 @@ class HomeScreenVM(application: Application) : AndroidViewModel(application) { fun reloadSongs(mainVM: MainVM){ + _menuExpanded.update { false } + + if(preferences.getBoolean("indexingSongs", false)) return + Toast.makeText(context, getAppString(context, R.string.IndexingSongs), Toast.LENGTH_LONG).show() viewModelScope.launch { withContext(Dispatchers.IO){ - _menuExpanded.update { false } - _syncingSongs.update { true } + preferences.edit().putBoolean("indexingSongs", true).apply() + mainVM.indexSongs(showNotification = true, onFinish = { _syncingSongs.update { false } + preferences.edit().putBoolean("indexingSongs", false).apply() }) } } diff --git a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/main/MainScreen.kt b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/main/MainScreen.kt index 9fc645f..50e1f02 100644 --- a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/main/MainScreen.kt +++ b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/main/MainScreen.kt @@ -67,6 +67,7 @@ import java.net.URLDecoder @Composable fun MainScreen( mainVM: MainVM, + settingsVM: SettingsVM, activityContext: ViewModelStoreOwner, rootNavController: NavHostController, onGetPlaylistImage: () -> Unit, @@ -109,6 +110,7 @@ fun MainScreen( context, activityContext, mainVM, + settingsVM, hideNavbarMotionProgress, playerSheetScaffoldState, miniPlayerHeight, @@ -126,6 +128,7 @@ fun MainScreen( context, activityContext, mainVM, + settingsVM, playerSheetScaffoldState, miniPlayerHeight, currentSong, @@ -146,6 +149,7 @@ fun PortraitScreen( context: Context, activityContext: ViewModelStoreOwner, mainVM: MainVM, + settingsVM: SettingsVM, hideNavbarMotionProgress: Float, playerSheetScaffoldState: BottomSheetScaffoldState, miniPlayerHeight: Dp, @@ -176,6 +180,7 @@ fun PortraitScreen( MainScaffold( activityContext, mainVM, + settingsVM, playerSheetScaffoldState, miniPlayerHeight, currentSong, @@ -206,6 +211,7 @@ fun LandscapeScreen( context: Context, activityContext: ViewModelStoreOwner, mainVM: MainVM, + settingsVM: SettingsVM, playerSheetScaffoldState: BottomSheetScaffoldState, miniPlayerHeight: Dp, currentSong: Song?, @@ -241,6 +247,7 @@ fun LandscapeScreen( MainScaffold( activityContext, mainVM, + settingsVM, playerSheetScaffoldState, miniPlayerHeight, currentSong, @@ -260,6 +267,7 @@ fun LandscapeScreen( fun MainScaffold( activityContext: ViewModelStoreOwner, mainVM: MainVM, + settingsVM: SettingsVM, playerSheetState: BottomSheetScaffoldState, miniPlayerHeight: Dp, currentSong: Song?, @@ -308,6 +316,7 @@ fun MainScaffold( Player( mainVM = mainVM, + settingsVM = settingsVM, vm = ViewModelProvider(activityContext)[PlayerScreenVM::class.java], selectedSong = currentSong, queue = queue, diff --git a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/player/PlayerScreen.kt b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/player/PlayerScreen.kt index 87d4af7..9615b10 100644 --- a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/player/PlayerScreen.kt +++ b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/player/PlayerScreen.kt @@ -2,11 +2,13 @@ package com.lighttigerxiv.simple.mp.compose.screens.main.player import android.content.Context import android.content.res.Configuration +import android.view.View import androidx.compose.foundation.* import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsDraggedAsState import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.* @@ -20,11 +22,13 @@ import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.layoutId import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.compose.ui.viewinterop.AndroidView import androidx.constraintlayout.compose.ExperimentalMotionApi import androidx.constraintlayout.compose.MotionLayout import androidx.constraintlayout.compose.MotionScene @@ -42,14 +46,18 @@ import com.lighttigerxiv.simple.mp.compose.data.variables.SMALL_SPACING import com.lighttigerxiv.simple.mp.compose.data.variables.XSMALL_SPACING import com.lighttigerxiv.simple.mp.compose.functions.getImage import com.lighttigerxiv.simple.mp.compose.screens.main.playlists.playlist.modifyIf +import com.lighttigerxiv.simple.mp.compose.settings.SettingsVM import com.lighttigerxiv.simple.mp.compose.ui.composables.ClickableMediumIcon import com.lighttigerxiv.simple.mp.compose.ui.composables.CustomText import com.lighttigerxiv.simple.mp.compose.ui.composables.ReorderableSongItem +import com.lighttigerxiv.simple.mp.compose.ui.composables.spacers.MediumHorizontalSpacer +import com.lighttigerxiv.simple.mp.compose.ui.composables.spacers.MediumVerticalSpacer import com.lighttigerxiv.simple.mp.compose.ui.composables.spacers.SmallVerticalSpacer import com.lighttigerxiv.simple.mp.compose.ui.composables.spacers.SmallHorizontalSpacer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.drop import kotlinx.coroutines.launch +import org.burnoutcrew.reorderable.ItemPosition import org.burnoutcrew.reorderable.ReorderableItem import org.burnoutcrew.reorderable.rememberReorderableLazyListState import org.burnoutcrew.reorderable.reorderable @@ -58,6 +66,7 @@ import org.burnoutcrew.reorderable.reorderable @Composable fun Player( mainVM: MainVM, + settingsVM: SettingsVM, vm: PlayerScreenVM, selectedSong: Song, queue: List?, @@ -81,6 +90,8 @@ fun Player( val inPortrait = localConfiguration.orientation == Configuration.ORIENTATION_PORTRAIT val showMenu = vm.showMenu.collectAsState().value val hideNavMotionProgress = mainVM.hideNavProgress.collectAsState().value + val showCarPlayer = mainVM.showCarPlayer.collectAsState().value + val keepScreenOnInCarMode = settingsVM.keepScreenOnInCarModeSetting.collectAsState().value LaunchedEffect(queue) { songsCoversPager.scrollToPage(mainVM.songPosition.value) @@ -125,10 +136,14 @@ fun Player( if (currentTab == 1) { vm.highlightTab("queue") + } } } + + + MotionLayout( modifier = Modifier .fillMaxSize(), @@ -158,49 +173,105 @@ fun Player( if (inPortrait) { - PortraitPlayer( - context, - scope, - vm, - mainVM, - surfaceColor, - selectedSong, - queue, - upNextQueue, - musicPlaying, - songSeconds, - songAndQueuePager, - songsCoversPager, - showMenu, - onOpenPage = { onOpenPage(it) }, - currentSongMinutesAndSecondsText, - songMinutesAndSecondsText, - queueShuffled, - songOnRepeat, - onClosePLayer = { onClosePLayer() } - ) + + if (showCarPlayer) { + PortraitCarPlayer( + context, + scope, + vm, + mainVM, + selectedSong, + queue, + upNextQueue, + musicPlaying, + songSeconds, + songAndQueuePager, + songsCoversPager, + currentSongMinutesAndSecondsText, + songMinutesAndSecondsText, + queueShuffled, + songOnRepeat, + onClosePLayer = { onClosePLayer() } + ) + + if(keepScreenOnInCarMode){ + KeepScreenOn() + } + } else { + PortraitPlayer( + context, + scope, + vm, + mainVM, + settingsVM, + surfaceColor, + selectedSong, + queue, + upNextQueue, + musicPlaying, + songSeconds, + songAndQueuePager, + songsCoversPager, + showMenu, + onOpenPage = { onOpenPage(it) }, + currentSongMinutesAndSecondsText, + songMinutesAndSecondsText, + queueShuffled, + songOnRepeat, + onClosePLayer = { onClosePLayer() }, + showCarPlayer + ) + } + } else { - LandscapePlayer( - context, - scope, - vm, - mainVM, - surfaceColor, - selectedSong, - queue, - upNextQueue, - musicPlaying, - songSeconds, - songAndQueuePager, - songsCoversPager, - showMenu, - onOpenPage = { onOpenPage(it) }, - currentSongMinutesAndSecondsText, - songMinutesAndSecondsText, - queueShuffled, - songOnRepeat, - onClosePLayer = { onClosePLayer() } - ) + + if(showCarPlayer){ + LandscapeCarPlayer( + context, + scope, + vm, + mainVM, + selectedSong, + queue, + upNextQueue, + musicPlaying, + songSeconds, + songAndQueuePager, + songsCoversPager, + currentSongMinutesAndSecondsText, + songMinutesAndSecondsText, + queueShuffled, + songOnRepeat, + onClosePLayer = { onClosePLayer() } + ) + + if(keepScreenOnInCarMode){ + KeepScreenOn() + } + }else{ + LandscapePlayer( + context, + scope, + vm, + mainVM, + settingsVM, + surfaceColor, + selectedSong, + queue, + upNextQueue, + musicPlaying, + songSeconds, + songAndQueuePager, + songsCoversPager, + showMenu, + onOpenPage = { onOpenPage(it) }, + currentSongMinutesAndSecondsText, + songMinutesAndSecondsText, + queueShuffled, + songOnRepeat, + onClosePLayer = { onClosePLayer() } + ) + } } } } @@ -215,6 +286,7 @@ fun PortraitPlayer( scope: CoroutineScope, vm: PlayerScreenVM, mainVM: MainVM, + settingsVM: SettingsVM, surfaceColor: Color, currentSong: Song, queue: List?, @@ -229,7 +301,8 @@ fun PortraitPlayer( songMinutesAndSecondsText: String, queueShuffled: Boolean, songOnRepeat: Boolean, - onClosePLayer: () -> Unit + onClosePLayer: () -> Unit, + showCarPlayer: Boolean ) { val defaultAlbumArt = remember { getImage(context, R.drawable.cd, ImageSizes.LARGE).asImageBitmap() } @@ -275,7 +348,6 @@ fun PortraitPlayer( .background(MaterialTheme.colorScheme.surface) ) { - Row( modifier = Modifier .clip(CircleShape) @@ -372,7 +444,9 @@ fun PortraitPlayer( .background(mainVM.surfaceColor.collectAsState().value), expanded = showMenu, onDismissRequest = { - vm.updateShowMenu(false) + if (!showCarPlayer) { + vm.updateShowMenu(false) + } }, ) { @@ -409,6 +483,18 @@ fun PortraitPlayer( vm.updateShowMenu(false) } ) + + if (settingsVM.carPlayerSetting.collectAsState().value) { + DropdownMenuItem( + text = { + CustomText(text = stringResource(id = R.string.ShowCarPlayer)) + }, + onClick = { + + mainVM.updateShowCarPlayer(true) + } + ) + } } } } @@ -675,14 +761,17 @@ fun PortraitPlayer( .fillMaxSize() .then(Modifier.reorderable(state)) ) { - items(items = upNextQueue, key = { it.id }) { song -> + itemsIndexed(items = upNextQueue, key = { _, song -> song.id }) { index, song -> ReorderableItem(state, key = song.id) { isDragging -> ReorderableSongItem( mainVM = mainVM, song = song, state = state, - isDragging = isDragging + isDragging = isDragging, + onClick = { + mainVM.onUpNextQueueMove(ItemPosition(index, song.id), ItemPosition(0, upNextQueue[0].id)) + } ) } } @@ -696,12 +785,11 @@ fun PortraitPlayer( @OptIn(ExperimentalPagerApi::class) @Composable -fun LandscapePlayer( +fun PortraitCarPlayer( context: Context, scope: CoroutineScope, vm: PlayerScreenVM, mainVM: MainVM, - surfaceColor: Color, currentSong: Song, queue: List?, upNextQueue: List?, @@ -709,8 +797,6 @@ fun LandscapePlayer( songSeconds: Float, songAndQueuePager: PagerState, songsCoversPager: PagerState, - showMenu: Boolean, - onOpenPage: (page: String) -> Unit, currentSongMinutesAndSecondsText: String, songMinutesAndSecondsText: String, queueShuffled: Boolean, @@ -718,7 +804,6 @@ fun LandscapePlayer( onClosePLayer: () -> Unit ) { - val defaultAlbumArt = remember { getImage(context, R.drawable.cd, ImageSizes.LARGE).asImageBitmap() } var secondsSliderValue by remember { mutableStateOf(songSeconds) } val secondsSliderInteractionSource = remember { MutableInteractionSource() } val draggingSecondsSlider = secondsSliderInteractionSource.collectIsDraggedAsState().value @@ -732,14 +817,13 @@ fun LandscapePlayer( } Column( - modifier = Modifier - .fillMaxSize() + modifier = Modifier.fillMaxSize() ) { Row( modifier = Modifier - .fillMaxWidth() - + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically ) { ClickableMediumIcon( @@ -748,6 +832,8 @@ fun LandscapePlayer( color = MaterialTheme.colorScheme.onSurface ) + MediumHorizontalSpacer() + Row( modifier = Modifier .fillMaxWidth() @@ -772,12 +858,13 @@ fun LandscapePlayer( } } .padding( - top = 12.dp, - bottom = 12.dp, + top = 10.dp, + bottom = 10.dp, start = MEDIUM_SPACING, end = MEDIUM_SPACING ), - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center ) { Icon( @@ -809,12 +896,13 @@ fun LandscapePlayer( } } .padding( - top = 12.dp, - bottom = 12.dp, + top = 10.dp, + bottom = 10.dp, start = MEDIUM_SPACING, end = MEDIUM_SPACING ), - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center ) { Icon( @@ -837,6 +925,8 @@ fun LandscapePlayer( } } + MediumHorizontalSpacer() + Box( modifier = Modifier .height(35.dp) @@ -845,147 +935,1058 @@ fun LandscapePlayer( if (songAndQueuePager.currentPage == 0) { ClickableMediumIcon( - id = R.drawable.vertical_three_dots, + id = R.drawable.smartphone, onClick = { - vm.updateShowMenu(true) + + mainVM.updateShowCarPlayer(false) + vm.updateShowMenu(false) }, color = MaterialTheme.colorScheme.onSurface ) + } + } + } + HorizontalPager( + modifier = Modifier.fillMaxSize(), + state = songAndQueuePager + ) { selectedPage -> - DropdownMenu( + if (selectedPage == 0) { + Column { + + Column( modifier = Modifier - .background(mainVM.surfaceColor.collectAsState().value), - expanded = showMenu, - onDismissRequest = { - vm.updateShowMenu(false) - }, + .fillMaxWidth() + .fillMaxHeight() + .weight(1f) ) { + HorizontalPager( + state = songsCoversPager, + itemSpacing = SMALL_SPACING, + ) { currentCoverIndex -> - DropdownMenuItem( - text = { - CustomText(text = stringResource(id = R.string.GoToArtist)) - }, - onClick = { - - onOpenPage("${Routes.Root.FLOATING_ARTIST}${currentSong.artistID}") - vm.updateShowMenu(false) - } - ) - - DropdownMenuItem( - text = { - CustomText(text = stringResource(id = R.string.GoToAlbum)) - }, - onClick = { - - onOpenPage("${Routes.Root.FLOATING_ALBUM}${currentSong.albumID}") - vm.updateShowMenu(false) - } - ) - - DropdownMenuItem( - text = { - CustomText(text = stringResource(id = R.string.AddToPlaylist)) - }, - onClick = { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + queue?.get(currentCoverIndex)?.let { + CustomText( + modifier = Modifier, + text = it.title, + size = 40.sp, + color = MaterialTheme.colorScheme.primary + ) + } - onOpenPage("${Routes.Root.ADD_SONG_TO_PLAYLIST}${currentSong.id}") + MediumVerticalSpacer() - vm.updateShowMenu(false) + queue?.get(currentCoverIndex)?.let { + CustomText( + text = mainVM.getSongArtist(it).name, + size = 30.sp + ) + } } - ) + } } - } - } - } - Spacer(modifier = Modifier.height(32.dp)) + Slider( + value = secondsSliderValue, + interactionSource = secondsSliderInteractionSource, + onValueChange = { - HorizontalPager( - modifier = Modifier.fillMaxSize(), - state = songAndQueuePager - ) { selectedPage -> + secondsSliderValue = it + mainVM.updateCurrentSongMinutesAndSecondsText(mainVM.getMinutesAndSeconds(it.toInt())) + }, + onValueChangeFinished = { - Row( - modifier = Modifier.fillMaxSize() - ) { + if (!musicPlaying) + mainVM.pauseResumeMusic() - //Song Tab - if (selectedPage == 0) { + mainVM.seekSongSeconds(secondsSliderValue.toInt()) + }, + valueRange = 1f..(currentSong.duration / 1000).toFloat(), + colors = SliderDefaults.colors( + thumbColor = MaterialTheme.colorScheme.primary, + activeTrackColor = MaterialTheme.colorScheme.primary, + inactiveTrackColor = MaterialTheme.colorScheme.onSurfaceVariant + ) + ) Row( modifier = Modifier - .weight(0.4f) - .fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center + .fillMaxWidth() + .wrapContentHeight() ) { - HorizontalPager( - state = songsCoversPager, - itemSpacing = SMALL_SPACING - ) { currentCoverIndex -> + Text( + modifier = Modifier.offset(x = 8.dp), + text = currentSongMinutesAndSecondsText, + color = MaterialTheme.colorScheme.onSurface + ) - val songAlbumArt = mainVM.getSongArt(queue!![currentCoverIndex]) + Spacer(modifier = Modifier.weight(1f, fill = true)) - Image( - modifier = Modifier - .fillMaxHeight() - .aspectRatio(1f) - .clip(RoundedCornerShape(14.dp)) - .modifyIf(songAlbumArt == null) { - background(surfaceColor) - } - .modifyIf(songAlbumArt == null) { - padding(5.dp) - }, - bitmap = songAlbumArt?.asImageBitmap() ?: defaultAlbumArt, - colorFilter = if (songAlbumArt == null) ColorFilter.tint(MaterialTheme.colorScheme.primary) else null, - contentDescription = "Album Art" - ) - } + Text( + modifier = Modifier.offset(x = (-8).dp), + text = songMinutesAndSecondsText, + color = MaterialTheme.colorScheme.onSurface + ) } + MediumVerticalSpacer() + Column( modifier = Modifier - .weight(0.6f) - .fillMaxWidth() .fillMaxHeight() + .weight(1f) ) { - Column( + Row( modifier = Modifier - .fillMaxWidth() - ) { - - CustomText( - text = currentSong.title, - size = 24.sp, - weight = FontWeight.Bold, - color = MaterialTheme.colorScheme.primary - ) + .fillMaxHeight() + .weight(1f, fill = true) + ){ + Row( + modifier = Modifier + .fillMaxWidth() + .weight(1f, fill = true), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ){ + Icon( + painter = painterResource(id = R.drawable.previous), + contentDescription = "Select Previous Song", + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier + .fillMaxSize() + .padding(20.dp) + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { + mainVM.selectPreviousSong() + } + ) + } - CustomText( + Row( modifier = Modifier - .clickable { onOpenPage("${Routes.Root.FLOATING_ARTIST}${currentSong.artistID}") }, - text = mainVM.getSongArtist(currentSong).name, - size = 16.sp - ) - } + .fillMaxWidth() + .weight(1f, fill = true), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ){ + Icon( + bitmap = if (musicPlaying) { + remember { getImage(context, R.drawable.icon_pause_round_solid, ImageSizes.MEDIUM) } + } else { + remember { getImage(context, R.drawable.icon_play_round_solid, ImageSizes.MEDIUM) } + }.asImageBitmap(), + contentDescription = "Play/Pause Button", + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier + .fillMaxSize() + .aspectRatio(1f) + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { + mainVM.pauseResumeMusic() + } + ) + } + Row( + modifier = Modifier + .fillMaxWidth() + .weight(1f, fill = true), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ){ + Icon( + painter = painterResource(id = R.drawable.next), + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier + .padding(20.dp) + .fillMaxSize() + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { - Slider( - value = secondsSliderValue, - interactionSource = secondsSliderInteractionSource, - onValueChange = { + mainVM.selectNextSong() + } + ) + } + } - secondsSliderValue = it - mainVM.updateCurrentSongMinutesAndSecondsText(mainVM.getMinutesAndSeconds(it.toInt())) - }, - onValueChangeFinished = { + Row( + modifier = Modifier + .fillMaxHeight() + .weight(1f, fill = true) + .padding(40.dp), + ){ + Column( + modifier = Modifier + .fillMaxWidth() + .weight(1f, fill = true), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ){ - if (!musicPlaying) + Icon( + painter = painterResource(id = R.drawable.shuffle), + contentDescription = "", + tint = if (queueShuffled) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface, + modifier = Modifier + .fillMaxHeight() + .weight(1f, fill = true) + .aspectRatio(1f) + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { + + mainVM.toggleShuffle() + } + ) + + if (queueShuffled) { + Dot(carMode = true) + }else{ + Spacer(modifier = Modifier.height(8.dp)) + } + } + + Column( + modifier = Modifier + .fillMaxWidth() + .weight(1f, fill = true), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ){ + + Icon( + painter = painterResource(id = R.drawable.repeat), + contentDescription = "Repeat Song", + tint = if (songOnRepeat) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface, + modifier = Modifier + .fillMaxHeight() + .weight(1f, fill = true) + .aspectRatio(1f) + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { + mainVM.toggleRepeat() + } + ) + + if (songOnRepeat) { + Dot(carMode = true) + }else{ + Spacer(modifier = Modifier.height(8.dp)) + } + } + } + } + } + } + + if (selectedPage == 1) { + + Column { + + MediumVerticalSpacer() + + if (upNextQueue!!.isEmpty()) { + + Column( + modifier = Modifier + .fillMaxSize(), + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.CenterHorizontally + ) { + + SmallVerticalSpacer() + + CustomText(text = stringResource(id = R.string.Shrug)) + } + } else { + + val state = rememberReorderableLazyListState(onMove = mainVM::onUpNextQueueMove) + + androidx.compose.foundation.lazy.LazyColumn( + state = state.listState, + modifier = Modifier + .fillMaxSize() + .then(Modifier.reorderable(state)) + ) { + itemsIndexed(items = upNextQueue, key = {_, song -> song.id }) { index, song -> + ReorderableItem(state, key = song.id) { isDragging -> + + ReorderableSongItem( + mainVM = mainVM, + song = song, + state = state, + isDragging = isDragging, + carMode = true, + onClick = { + mainVM.onUpNextQueueMove(ItemPosition(index, song.id), ItemPosition(0, upNextQueue[0].id)) + } + ) + } + } + } + } + } + } + } + } +} + +@OptIn(ExperimentalPagerApi::class) +@Composable +fun LandscapePlayer( + context: Context, + scope: CoroutineScope, + vm: PlayerScreenVM, + mainVM: MainVM, + settingsVM: SettingsVM, + surfaceColor: Color, + currentSong: Song, + queue: List?, + upNextQueue: List?, + musicPlaying: Boolean, + songSeconds: Float, + songAndQueuePager: PagerState, + songsCoversPager: PagerState, + showMenu: Boolean, + onOpenPage: (page: String) -> Unit, + currentSongMinutesAndSecondsText: String, + songMinutesAndSecondsText: String, + queueShuffled: Boolean, + songOnRepeat: Boolean, + onClosePLayer: () -> Unit +) { + + val defaultAlbumArt = remember { getImage(context, R.drawable.cd, ImageSizes.LARGE).asImageBitmap() } + var secondsSliderValue by remember { mutableStateOf(songSeconds) } + val secondsSliderInteractionSource = remember { MutableInteractionSource() } + val draggingSecondsSlider = secondsSliderInteractionSource.collectIsDraggedAsState().value + + LaunchedEffect(songSeconds) { + if (!draggingSecondsSlider) { + secondsSliderValue = songSeconds + } + + mainVM.updateCurrentSongMinutesAndSecondsText(mainVM.getMinutesAndSeconds((secondsSliderValue).toInt())) + } + + Column( + modifier = Modifier + .fillMaxSize() + ) { + + Row( + modifier = Modifier + .fillMaxWidth() + + ) { + + ClickableMediumIcon( + id = R.drawable.icon_arrow_down_solid, + onClick = { onClosePLayer() }, + color = MaterialTheme.colorScheme.onSurface + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .weight(1f, fill = true), + horizontalArrangement = Arrangement.Center + ) { + + Row( + modifier = Modifier + .clip(CircleShape) + .background(MaterialTheme.colorScheme.surface) + ) { + + + Row( + modifier = Modifier + .clip(CircleShape) + .background(if (songAndQueuePager.currentPage == 0) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface) + .clickable { + scope.launch { + songAndQueuePager.scrollToPage(0) + } + } + .padding( + top = 12.dp, + bottom = 12.dp, + start = MEDIUM_SPACING, + end = MEDIUM_SPACING + ), + verticalAlignment = Alignment.CenterVertically + ) { + + Icon( + modifier = Modifier + .height(18.dp) + .width(18.dp), + painter = painterResource(id = R.drawable.music_note), + contentDescription = null, + tint = if (songAndQueuePager.currentPage == 0) MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.onSurface + ) + + SmallHorizontalSpacer() + + CustomText( + + text = "Song", + color = if (songAndQueuePager.currentPage == 0) MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.onSurface + ) + } + + + Row( + modifier = Modifier + .clip(CircleShape) + .background(if (songAndQueuePager.currentPage == 1) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface) + .clickable { + scope.launch { + songAndQueuePager.scrollToPage(1) + } + } + .padding( + top = 12.dp, + bottom = 12.dp, + start = MEDIUM_SPACING, + end = MEDIUM_SPACING + ), + verticalAlignment = Alignment.CenterVertically + ) { + + Icon( + modifier = Modifier + .height(18.dp) + .width(18.dp), + painter = painterResource(id = R.drawable.queue_list), + contentDescription = null, + tint = if (songAndQueuePager.currentPage == 1) MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.onSurface + ) + + SmallHorizontalSpacer() + + CustomText( + + text = "Up Next", + color = if (songAndQueuePager.currentPage == 1) MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.onSurface + ) + } + } + } + + Box( + modifier = Modifier + .height(35.dp) + .width(35.dp) + ) { + + if (songAndQueuePager.currentPage == 0) { + ClickableMediumIcon( + id = R.drawable.vertical_three_dots, + onClick = { + vm.updateShowMenu(true) + }, + color = MaterialTheme.colorScheme.onSurface + ) + + + DropdownMenu( + modifier = Modifier + .background(mainVM.surfaceColor.collectAsState().value), + expanded = showMenu, + onDismissRequest = { + vm.updateShowMenu(false) + }, + ) { + + DropdownMenuItem( + text = { + CustomText(text = stringResource(id = R.string.GoToArtist)) + }, + onClick = { + + onOpenPage("${Routes.Root.FLOATING_ARTIST}${currentSong.artistID}") + vm.updateShowMenu(false) + } + ) + + DropdownMenuItem( + text = { + CustomText(text = stringResource(id = R.string.GoToAlbum)) + }, + onClick = { + + onOpenPage("${Routes.Root.FLOATING_ALBUM}${currentSong.albumID}") + vm.updateShowMenu(false) + } + ) + + DropdownMenuItem( + text = { + CustomText(text = stringResource(id = R.string.AddToPlaylist)) + }, + onClick = { + + onOpenPage("${Routes.Root.ADD_SONG_TO_PLAYLIST}${currentSong.id}") + + vm.updateShowMenu(false) + } + ) + + if (settingsVM.carPlayerSetting.collectAsState().value) { + DropdownMenuItem( + text = { + CustomText(text = stringResource(id = R.string.ShowCarPlayer)) + }, + onClick = { + + mainVM.updateShowCarPlayer(true) + } + ) + } + } + } + } + } + + Spacer(modifier = Modifier.height(32.dp)) + + HorizontalPager( + modifier = Modifier.fillMaxSize(), + state = songAndQueuePager + ) { selectedPage -> + + Row( + modifier = Modifier.fillMaxSize() + ) { + + //Song Tab + if (selectedPage == 0) { + + Row( + modifier = Modifier + .weight(0.4f) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + + HorizontalPager( + state = songsCoversPager, + itemSpacing = SMALL_SPACING + ) { currentCoverIndex -> + + val songAlbumArt = mainVM.getSongArt(queue!![currentCoverIndex]) + + Image( + modifier = Modifier + .fillMaxHeight() + .aspectRatio(1f) + .clip(RoundedCornerShape(14.dp)) + .modifyIf(songAlbumArt == null) { + background(surfaceColor) + } + .modifyIf(songAlbumArt == null) { + padding(5.dp) + }, + bitmap = songAlbumArt?.asImageBitmap() ?: defaultAlbumArt, + colorFilter = if (songAlbumArt == null) ColorFilter.tint(MaterialTheme.colorScheme.primary) else null, + contentDescription = "Album Art" + ) + } + } + + Column( + modifier = Modifier + .weight(0.6f) + .fillMaxWidth() + .fillMaxHeight() + ) { + + Column( + modifier = Modifier + .fillMaxWidth() + ) { + + CustomText( + text = currentSong.title, + size = 24.sp, + weight = FontWeight.Bold, + color = MaterialTheme.colorScheme.primary + ) + + CustomText( + modifier = Modifier + .clickable { onOpenPage("${Routes.Root.FLOATING_ARTIST}${currentSong.artistID}") }, + text = mainVM.getSongArtist(currentSong).name, + size = 16.sp + ) + } + + + Slider( + value = secondsSliderValue, + interactionSource = secondsSliderInteractionSource, + onValueChange = { + + secondsSliderValue = it + mainVM.updateCurrentSongMinutesAndSecondsText(mainVM.getMinutesAndSeconds(it.toInt())) + }, + onValueChangeFinished = { + + if (!musicPlaying) + mainVM.pauseResumeMusic() + + mainVM.seekSongSeconds(secondsSliderValue.toInt()) + }, + valueRange = 1f..(currentSong.duration / 1000).toFloat(), + colors = SliderDefaults.colors( + thumbColor = MaterialTheme.colorScheme.primary, + activeTrackColor = MaterialTheme.colorScheme.primary, + inactiveTrackColor = MaterialTheme.colorScheme.onSurfaceVariant + ) + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + ) { + + Text( + modifier = Modifier.offset(x = 8.dp), + text = currentSongMinutesAndSecondsText, + color = MaterialTheme.colorScheme.onSurface + ) + + Spacer(modifier = Modifier.weight(1f, fill = true)) + + Text( + modifier = Modifier.offset(x = (-8).dp), + text = songMinutesAndSecondsText, + color = MaterialTheme.colorScheme.onSurface + ) + } + + Row( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically + ) { + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.wrapContentHeight() + ) { + + if (queueShuffled) { + Spacer(modifier = Modifier.height(XSMALL_SPACING)) //Needed to keep shuffle button in place when shuffle is enabled + } + + Icon( + painter = painterResource(id = R.drawable.shuffle), + contentDescription = "", + tint = if (queueShuffled) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface, + modifier = Modifier + .size(35.dp) + .fillMaxSize() + .fillMaxSize() + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { + + mainVM.toggleShuffle() + } + ) + + if (queueShuffled) { + Dot() + } + } + + Icon( + painter = painterResource(id = R.drawable.previous), + contentDescription = "Select Previous Song", + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier + .size(35.dp) + .fillMaxSize() + .fillMaxSize() + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { + mainVM.selectPreviousSong() + } + ) + + Icon( + painter = if (musicPlaying) { + painterResource(id = R.drawable.icon_pause_round_solid) + } else { + painterResource(id = R.drawable.icon_play_round_solid) + }, + contentDescription = "Play/Pause Button", + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier + .size(80.dp) + .fillMaxSize() + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { + mainVM.pauseResumeMusic() + } + ) + + Icon( + painter = painterResource(id = R.drawable.next), + contentDescription = "Next Song", + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier + .size(35.dp) + .fillMaxSize() + .fillMaxSize() + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { + + mainVM.selectNextSong() + } + ) + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.wrapContentHeight() + ) { + + if (songOnRepeat) { + + Spacer(modifier = Modifier.height(XSMALL_SPACING)) //Needed to keep repeat button in place when repeat is enabled + } + + Icon( + painter = painterResource(id = R.drawable.repeat), + contentDescription = "Repeat Song", + tint = if (songOnRepeat) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface, + modifier = Modifier + .size(35.dp) + .fillMaxSize() + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { + mainVM.toggleRepeat() + } + ) + + if (songOnRepeat) { + Dot() + } + } + } + + } + + } + + //Queue Tab + if (selectedPage == 1) { + + if (upNextQueue!!.isEmpty()) { + + Column( + modifier = Modifier + .fillMaxSize(), + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.CenterHorizontally + ) { + + SmallVerticalSpacer() + + CustomText(text = stringResource(id = R.string.Shrug)) + } + } else { + + val state = rememberReorderableLazyListState(onMove = mainVM::onUpNextQueueMove) + + androidx.compose.foundation.lazy.LazyColumn( + state = state.listState, + modifier = Modifier + .fillMaxSize() + .then(Modifier.reorderable(state)) + ) { + itemsIndexed(upNextQueue, { _, song -> song.id }) { index, song -> + ReorderableItem(state, key = song.id) { isDragging -> + + ReorderableSongItem( + mainVM = mainVM, + song = song, + state = state, + isDragging = isDragging, + onClick = { + mainVM.onUpNextQueueMove(ItemPosition(index, song.id), ItemPosition(0, upNextQueue[0].id)) + } + ) + } + } + } + } + } + } + } + } +} + +@OptIn(ExperimentalPagerApi::class) +@Composable +fun LandscapeCarPlayer( + context: Context, + scope: CoroutineScope, + vm: PlayerScreenVM, + mainVM: MainVM, + currentSong: Song, + queue: List?, + upNextQueue: List?, + musicPlaying: Boolean, + songSeconds: Float, + songAndQueuePager: PagerState, + songsCoversPager: PagerState, + currentSongMinutesAndSecondsText: String, + songMinutesAndSecondsText: String, + queueShuffled: Boolean, + songOnRepeat: Boolean, + onClosePLayer: () -> Unit +) { + + var secondsSliderValue by remember { mutableStateOf(songSeconds) } + val secondsSliderInteractionSource = remember { MutableInteractionSource() } + val draggingSecondsSlider = secondsSliderInteractionSource.collectIsDraggedAsState().value + + LaunchedEffect(songSeconds) { + if (!draggingSecondsSlider) { + secondsSliderValue = songSeconds + } + + mainVM.updateCurrentSongMinutesAndSecondsText(mainVM.getMinutesAndSeconds((secondsSliderValue).toInt())) + } + + Column( + modifier = Modifier.fillMaxSize() + ) { + + Row( + modifier = Modifier + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + + ClickableMediumIcon( + id = R.drawable.icon_arrow_down_solid, + onClick = { onClosePLayer() }, + color = MaterialTheme.colorScheme.onSurface + ) + + MediumHorizontalSpacer() + + Row( + modifier = Modifier + .fillMaxWidth() + .weight(1f, fill = true), + horizontalArrangement = Arrangement.Center + ) { + + Row( + modifier = Modifier + .clip(CircleShape) + .background(MaterialTheme.colorScheme.surface) + ) { + + + Row( + modifier = Modifier + .clip(CircleShape) + .background(if (songAndQueuePager.currentPage == 0) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface) + .clickable { + scope.launch { + songAndQueuePager.scrollToPage(0) + } + } + .padding( + top = 10.dp, + bottom = 10.dp, + start = MEDIUM_SPACING, + end = MEDIUM_SPACING + ), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + + Icon( + modifier = Modifier + .height(18.dp) + .width(18.dp), + painter = painterResource(id = R.drawable.music_note), + contentDescription = null, + tint = if (songAndQueuePager.currentPage == 0) MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.onSurface + ) + + SmallHorizontalSpacer() + + CustomText( + + text = "Song", + color = if (songAndQueuePager.currentPage == 0) MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.onSurface + ) + } + + + Row( + modifier = Modifier + .clip(CircleShape) + .background(if (songAndQueuePager.currentPage == 1) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface) + .clickable { + scope.launch { + songAndQueuePager.scrollToPage(1) + } + } + .padding( + top = 10.dp, + bottom = 10.dp, + start = MEDIUM_SPACING, + end = MEDIUM_SPACING + ), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + + Icon( + modifier = Modifier + .height(18.dp) + .width(18.dp), + painter = painterResource(id = R.drawable.queue_list), + contentDescription = null, + tint = if (songAndQueuePager.currentPage == 1) MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.onSurface + ) + + SmallHorizontalSpacer() + + CustomText( + + text = "Up Next", + color = if (songAndQueuePager.currentPage == 1) MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.onSurface + ) + } + } + } + + MediumHorizontalSpacer() + + Box( + modifier = Modifier + .height(35.dp) + .width(35.dp) + ) { + + if (songAndQueuePager.currentPage == 0) { + ClickableMediumIcon( + id = R.drawable.smartphone, + onClick = { + mainVM.updateShowCarPlayer(false) + vm.updateShowMenu(false) + }, + color = MaterialTheme.colorScheme.onSurface + ) + } + } + } + + HorizontalPager( + modifier = Modifier.fillMaxSize(), + state = songAndQueuePager + ) { selectedPage -> + + if (selectedPage == 0) { + Row { + + Column( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight() + .weight(1f) + ) { + HorizontalPager( + state = songsCoversPager, + itemSpacing = SMALL_SPACING, + ) { currentCoverIndex -> + + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + queue?.get(currentCoverIndex)?.let { + CustomText( + modifier = Modifier, + text = it.title, + size = 40.sp, + color = MaterialTheme.colorScheme.primary + ) + } + + MediumVerticalSpacer() + + queue?.get(currentCoverIndex)?.let { + CustomText( + text = mainVM.getSongArtist(it).name, + size = 30.sp + ) + } + } + } + } + + Column( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .fillMaxHeight() + ) { + + Slider( + value = secondsSliderValue, + interactionSource = secondsSliderInteractionSource, + onValueChange = { + + secondsSliderValue = it + mainVM.updateCurrentSongMinutesAndSecondsText(mainVM.getMinutesAndSeconds(it.toInt())) + }, + onValueChangeFinished = { + + if (!musicPlaying) mainVM.pauseResumeMusic() mainVM.seekSongSeconds(secondsSliderValue.toInt()) @@ -1019,31 +2020,110 @@ fun LandscapePlayer( ) } + MediumVerticalSpacer() + Row( modifier = Modifier - .wrapContentHeight() - .fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceEvenly, - verticalAlignment = Alignment.CenterVertically - ) { + .fillMaxHeight() + .weight(1f, fill = true) + ){ + Row( + modifier = Modifier + .fillMaxWidth() + .weight(1f, fill = true), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ){ + Icon( + painter = painterResource(id = R.drawable.previous), + contentDescription = "Select Previous Song", + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier + .fillMaxSize() + .padding(5.dp) + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { + mainVM.selectPreviousSong() + } + ) + } - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.wrapContentHeight() - ) { + Row( + modifier = Modifier + .fillMaxWidth() + .weight(1f, fill = true), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ){ + Icon( + bitmap = if (musicPlaying) { + remember { getImage(context, R.drawable.icon_pause_round_solid, ImageSizes.MEDIUM) } + } else { + remember { getImage(context, R.drawable.icon_play_round_solid, ImageSizes.MEDIUM) } + }.asImageBitmap(), + contentDescription = "Play/Pause Button", + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier + .fillMaxSize() + .aspectRatio(1f) + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { + mainVM.pauseResumeMusic() + } + ) + } - if (queueShuffled) { - Spacer(modifier = Modifier.height(XSMALL_SPACING)) //Needed to keep shuffle button in place when shuffle is enabled - } + Row( + modifier = Modifier + .fillMaxWidth() + .weight(1f, fill = true), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ){ + Icon( + painter = painterResource(id = R.drawable.next), + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier + .padding(5.dp) + .fillMaxSize() + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { + + mainVM.selectNextSong() + } + ) + } + } + + Row( + modifier = Modifier + .fillMaxHeight() + .weight(1f, fill = true) + .padding(10.dp), + ){ + Column( + modifier = Modifier + .fillMaxWidth() + .weight(1f, fill = true), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ){ Icon( painter = painterResource(id = R.drawable.shuffle), contentDescription = "", tint = if (queueShuffled) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface, modifier = Modifier - .size(35.dp) - .fillMaxSize() - .fillMaxSize() + .fillMaxHeight() + .weight(1f, fill = true) + .aspectRatio(1f) .clickable( indication = null, interactionSource = remember { MutableInteractionSource() } @@ -1054,79 +2134,28 @@ fun LandscapePlayer( ) if (queueShuffled) { - Dot() + Dot(carMode = true) + }else{ + Spacer(modifier = Modifier.height(8.dp)) } } - Icon( - painter = painterResource(id = R.drawable.previous), - contentDescription = "Select Previous Song", - tint = MaterialTheme.colorScheme.onSurface, - modifier = Modifier - .size(35.dp) - .fillMaxSize() - .fillMaxSize() - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - mainVM.selectPreviousSong() - } - ) - - Icon( - painter = if (musicPlaying) { - painterResource(id = R.drawable.icon_pause_round_solid) - } else { - painterResource(id = R.drawable.icon_play_round_solid) - }, - contentDescription = "Play/Pause Button", - tint = MaterialTheme.colorScheme.onSurface, - modifier = Modifier - .size(80.dp) - .fillMaxSize() - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - mainVM.pauseResumeMusic() - } - ) - - Icon( - painter = painterResource(id = R.drawable.next), - contentDescription = "Next Song", - tint = MaterialTheme.colorScheme.onSurface, - modifier = Modifier - .size(35.dp) - .fillMaxSize() - .fillMaxSize() - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - - mainVM.selectNextSong() - } - ) - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.wrapContentHeight() - ) { - - if (songOnRepeat) { - - Spacer(modifier = Modifier.height(XSMALL_SPACING)) //Needed to keep repeat button in place when repeat is enabled - } + modifier = Modifier + .fillMaxWidth() + .weight(1f, fill = true), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ){ Icon( painter = painterResource(id = R.drawable.repeat), contentDescription = "Repeat Song", tint = if (songOnRepeat) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface, modifier = Modifier - .size(35.dp) - .fillMaxSize() + .fillMaxHeight() + .weight(1f, fill = true) + .aspectRatio(1f) .clickable( indication = null, interactionSource = remember { MutableInteractionSource() } @@ -1136,17 +2165,21 @@ fun LandscapePlayer( ) if (songOnRepeat) { - Dot() + Dot(carMode = true) + }else{ + Spacer(modifier = Modifier.height(8.dp)) } } } - } - } + } - //Queue Tab - if (selectedPage == 1) { + if (selectedPage == 1) { + + Column { + + MediumVerticalSpacer() if (upNextQueue!!.isEmpty()) { @@ -1171,14 +2204,18 @@ fun LandscapePlayer( .fillMaxSize() .then(Modifier.reorderable(state)) ) { - items(upNextQueue, { it.id }) { song -> + itemsIndexed(items = upNextQueue, key = { _, song -> song.id }) { index, song -> ReorderableItem(state, key = song.id) { isDragging -> ReorderableSongItem( mainVM = mainVM, song = song, state = state, - isDragging = isDragging + isDragging = isDragging, + carMode = true, + onClick = { + mainVM.onUpNextQueueMove(ItemPosition(index, song.id), ItemPosition(0, upNextQueue[0].id)) + } ) } } @@ -1191,11 +2228,16 @@ fun LandscapePlayer( } @Composable -fun Dot() { +fun KeepScreenOn() = AndroidView({ View(it).apply { keepScreenOn = true } }) + +@Composable +fun Dot( + carMode: Boolean = false +) { Box( modifier = Modifier - .height(XSMALL_SPACING) - .width(XSMALL_SPACING) + .height(if (carMode) 8.dp else XSMALL_SPACING) + .width(if (carMode) 8.dp else XSMALL_SPACING) .clip(RoundedCornerShape(percent = 100)) .background(MaterialTheme.colorScheme.primary) ) diff --git a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/settings/SettingsScreen.kt b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/settings/SettingsScreen.kt index e270c27..5524ecb 100644 --- a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/settings/SettingsScreen.kt +++ b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/screens/main/settings/SettingsScreen.kt @@ -54,6 +54,8 @@ fun SettingsScreen( val filterAudioSetting = settingsVM.filterAudioSetting.collectAsState().value val downloadArtistCoverSetting = settingsVM.downloadArtistCoverSetting.collectAsState().value val downloadOverDataSetting = settingsVM.downloadOverDataSetting.collectAsState().value + val carPlayerSetting = settingsVM.carPlayerSetting.collectAsState().value + val keepScreenOnInCarModeSetting = settingsVM.keepScreenOnInCarModeSetting.collectAsState().value val selectedThemeMode = vm.selectedColorScheme.collectAsState().value val selectedDarkMode = vm.selectedDarkMode.collectAsState().value val filterAudioText = vm.filterAudioText.collectAsState().value @@ -79,8 +81,6 @@ fun SettingsScreen( SmallVerticalSpacer() - - Text( text = remember { getAppString(context, R.string.Theming) }, color = MaterialTheme.colorScheme.primary, @@ -118,7 +118,7 @@ fun SettingsScreen( ) - if(isAtLeastAndroid10()){ + if (isAtLeastAndroid10()) { DefaultSettingItem( icon = painterResource(id = R.drawable.brush), settingText = remember { getAppString(context, R.string.LightColorScheme) }, @@ -132,7 +132,7 @@ fun SettingsScreen( settingValue = darkColorScheme, onSettingClick = { onOpenScreen(Routes.Root.DARK_COLOR_SCHEMES) } ) - }else{ + } else { DefaultSettingItem( icon = painterResource(id = R.drawable.brush), settingText = remember { getAppString(context, R.string.ColorScheme) }, @@ -315,7 +315,6 @@ fun SettingsScreen( MediumVerticalSpacer() - Text( text = remember { getAppString(context, R.string.Audio) }, color = MaterialTheme.colorScheme.primary, @@ -389,6 +388,49 @@ fun SettingsScreen( ) } + MediumVerticalSpacer() + + Text( + text = remember { getAppString(context, R.string.ExperimentalFeatures) }, + color = MaterialTheme.colorScheme.primary, + fontSize = 22.sp, + textAlign = TextAlign.Start, + maxLines = 1, + modifier = Modifier + .fillMaxWidth() + .offset(8.dp) + ) + + Column( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .clip(RoundedCornerShape(14.dp)) + .background(MaterialTheme.colorScheme.surfaceVariant) + ) { + SwitchSettingItem( + icon = painterResource(id = R.drawable.car), + settingText = remember { getAppString(context, R.string.CarPlayer) }, + settingValue = carPlayerSetting, + onToggle = { + + settingsVM.updateCarPlayerSetting(!carPlayerSetting) + } + ) + + SwitchSettingItem( + icon = painterResource(id = R.drawable.car), + settingText = remember { getAppString(context, R.string.KeepScreenOnInCarMode) }, + settingValue = keepScreenOnInCarModeSetting, + enabled = carPlayerSetting, + onToggle = { + + settingsVM.updateKeepScreenOnCarModeSetting(!keepScreenOnInCarModeSetting) + } + ) + + } + if (showDarkModeDialog) { diff --git a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/services/SimpleMPService.kt b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/services/SimpleMPService.kt index 8b8bb73..f3032ee 100644 --- a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/services/SimpleMPService.kt +++ b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/services/SimpleMPService.kt @@ -460,6 +460,15 @@ class SimpleMPService : Service() { } catch (exc: Exception) { Log.e("Service Error", exc.toString()) + + //Removes song from queues + val song = getQueue()[currentSongPosition] + + queueList.remove(song) + shuffledQueueList.remove(song) + + currentSongPosition -= 1 + selectNextSong(context) } } diff --git a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/settings/SettingsVM.kt b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/settings/SettingsVM.kt index e27480e..e7f42f9 100644 --- a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/settings/SettingsVM.kt +++ b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/settings/SettingsVM.kt @@ -73,4 +73,18 @@ class SettingsVM(application: Application) : AndroidViewModel(application){ preferences.edit().putBoolean(Settings.DOWNLOAD_OVER_DATA, newValue).apply() _downloadOverDataSetting.update { newValue } } + + private val _carPlayerSetting = MutableStateFlow(preferences.getBoolean(Settings.CAR_PLAYER, false)) + val carPlayerSetting = _carPlayerSetting.asStateFlow() + fun updateCarPlayerSetting(newValue:Boolean) { + preferences.edit().putBoolean(Settings.CAR_PLAYER, newValue).apply() + _carPlayerSetting.update { newValue } + } + + private val _keepScreenOnInCarModeSetting = MutableStateFlow(preferences.getBoolean(Settings.KEEP_SCREEN_ON_IN_CAR_MODE, true)) + val keepScreenOnInCarModeSetting = _keepScreenOnInCarModeSetting.asStateFlow() + fun updateKeepScreenOnCarModeSetting(newValue:Boolean) { + preferences.edit().putBoolean(Settings.KEEP_SCREEN_ON_IN_CAR_MODE, newValue).apply() + _keepScreenOnInCarModeSetting.update { newValue } + } } \ No newline at end of file diff --git a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/ui/composables/SongItem.kt b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/ui/composables/SongItem.kt index 1c2f6ed..cadb252 100644 --- a/app/src/main/java/com/lighttigerxiv/simple/mp/compose/ui/composables/SongItem.kt +++ b/app/src/main/java/com/lighttigerxiv/simple/mp/compose/ui/composables/SongItem.kt @@ -3,6 +3,7 @@ package com.lighttigerxiv.simple.mp.compose.ui.composables import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape @@ -15,6 +16,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight @@ -174,7 +176,9 @@ fun ReorderableSongItem( modifier: Modifier = Modifier, song: Song, state: ReorderableLazyListState, - isDragging: Boolean + isDragging: Boolean, + carMode: Boolean = false, + onClick: () -> Unit = {} ) { val context = LocalContext.current @@ -187,9 +191,10 @@ fun ReorderableSongItem( Row( modifier = modifier .fillMaxWidth() - .height(70.dp) + .height(if(carMode) 100.dp else 70.dp) .clip(RoundedCornerShape(14.dp)) .background(if (isDragging) MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.surfaceVariant) + .clickable { onClick() } ) { AsyncImage( @@ -198,8 +203,8 @@ fun ReorderableSongItem( colorFilter = if (art == null) ColorFilter.tint(MaterialTheme.colorScheme.primary) else null, modifier = Modifier .clip(RoundedCornerShape(20)) - .height(70.dp) - .width(70.dp) + .height(if(carMode) 100.dp else 70.dp) + .width(if(carMode) 100.dp else 70.dp) .modifyIf(art == null) { background(surfaceColor) } @@ -248,11 +253,13 @@ fun ReorderableSongItem( Image( modifier = Modifier - .height(24.dp) + .height(if(carMode) 100.dp else 70.dp) + .width(if(carMode) 40.dp else 30.dp) .detectReorder(state), painter = painterResource(id = R.drawable.drag), contentDescription = "", - colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface) + colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface), + contentScale = ContentScale.Fit ) } diff --git a/app/src/main/res/drawable/car.xml b/app/src/main/res/drawable/car.xml new file mode 100644 index 0000000..8635635 --- /dev/null +++ b/app/src/main/res/drawable/car.xml @@ -0,0 +1,7 @@ + + + diff --git a/app/src/main/res/drawable/smartphone.xml b/app/src/main/res/drawable/smartphone.xml new file mode 100644 index 0000000..1d10e04 --- /dev/null +++ b/app/src/main/res/drawable/smartphone.xml @@ -0,0 +1,7 @@ + + + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml new file mode 100644 index 0000000..227b61b --- /dev/null +++ b/app/src/main/res/values-de/strings.xml @@ -0,0 +1,107 @@ + + + SimpleMP + Undefiniert + BEISPIEL + Füge Widget hinzu + Es spielt keine Musik. Zum Öffnen klicken. + Suche Songs + Suche Künstler + Suche Album + Suche Playlists + nach Änderungsdatum sortieren + Alphabetisch sortieren + Sortiere nach zuletzt Hinzugefügt + Sortiere nach älteste Hinzugefügt + Sortiere Aufsteigend + Sortiere Absteigend + Einstellungen + Über uns + Appversion + Quelle der Anwendung + Quellcode auf GitHub + Quellcode auf GitLab + Start + Genres + Deine Playlisten + Name der Playliste + Füge Name der Playliste hinzu + Erstellen + Abspielen + Shuffle + Bearbeite Playlist + Lösche Playlist + Möchten Sie die Wiedergabeliste wirklich löschen? + Abbrechen + Löschen + Keine Songs hinzugefügt + Künstler + Titel + Alben + Titel + Nächster Titel + Zum Künstler + Zum Album + Füge zur Playlist hinzu + Theming + Farbschema + Dunkler Modus + Akzentfarbe + Wähle Farbschema + Systemvoreinstellung + Heller Modus + Anwenden + Audio + Unter Audio filtern + Selektiere dunklen Modus + Farbe + Audio filtern + Gib minimale Sekunden ein. + System + Blau + Rot + Lila + Orange + Gelb + Grün + Rosa + Voreingestellt + Herzlich Willkommen zu Simple MP + Weiter + Erteilt + Erteilen + Berechtigungen + Andere Einstellungen + Erlauben Sie die Speicherberechtigung, um alle Musikdateien auf Ihrem Gerät laden zu können. + Erlauben Sie die Berechtigung für Benachrichtigungen, um Benachrichtigungen zur Musik anzeigen zu können. + Zurück + Akzente + Fertigstellen + Dieses Bild ist zu groß. Bitte versuchen Sie das Bild zu verkleinern. + Mudar capa de artista + Artista + Künstler-Cover ändern + Online Ergebnisse + Aktiviere das Internet um Cover zu erhalten. + Standard verwenden + Daten + Lade Künstler-Cover über das Internet herunter. + Lade Künstler-Cover über mobile Daten herunter. (möglicherweise fallen Kosten an) + Playlists + Dieser Titel ist bereits in der Playlist + Füge Titel hinzu + Playlist + Hinzufügen + Auswählen + Bild auswählen + Bild löschen + Changelog + Herunterladen + Von GitHub Releases herunterladen + Von Playstore herunterladen + Von F-Droid herunterladen + Songs aktualisieren + Schließen Sie die App nicht, während Sie Songs geladen werden!! + Schließen Sie die App nicht, während Sie Songs geladen werden!! + Songs werden geladen + \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 0901bc8..f3a3864 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -101,4 +101,12 @@ Recarregar músicas Não feche a aplicação enquanto as músicas recarregam A recarregar músicas + Esquema De Cores Escuro + Esquema De Cores Claro + A indexar m�sicas. N�o feche a aplicaç�o + Funcionalidades Experimentais + Player Do Carro + Manter Ecr� Ativo Em Modo Carro + Mostar Player Do Carro + Ordenar Por Artista \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0e77f03..359a921 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -118,4 +118,9 @@ Dark Color Scheme Light Color Scheme Indexing Songs. Don\'t close the app. + Experimental Features + Car Player + Keep Screen On In Car Mode + Show Car Player + Sort By Artist \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index cd0519b..3c7a8bd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,4 +20,4 @@ kotlin.code.style=official # Enables namespacing of each library's R class so that its R class includes only the # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true \ No newline at end of file +android.nonTransitiveRClass=true