diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a91674a9..b65a8452 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,23 +17,23 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout@v4 - name: Validate Gradle Wrapper - uses: gradle/wrapper-validation-action@v1 + uses: gradle/wrapper-validation-action@v2 - name: Setup JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: - distribution: 'temurin' java-version: 17 + distribution: "temurin" cache: "gradle" - name: Build APK run: bash ./gradlew assembleDebug --stacktrace - name: Upload APK - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: app path: app/build/outputs/apk/debug/*.apk diff --git a/.github/workflows/unsigned_release.yml b/.github/workflows/unsigned_release.yml new file mode 100644 index 00000000..a0579287 --- /dev/null +++ b/.github/workflows/unsigned_release.yml @@ -0,0 +1,29 @@ +name: Create Unsigned Release APK + +on: + workflow_dispatch: + +jobs: + debug-builds: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Validate Gradle Wrapper + uses: gradle/wrapper-validation-action@v2 + + - name: Setup JDK + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: "temurin" + + - name: Build APK + run: bash ./gradlew assembleRelease --stacktrace + + - name: Upload APK + uses: actions/upload-artifact@v4 + with: + name: app + path: app/build/outputs/apk/release/*.apk diff --git a/.gitignore b/.gitignore index aa724b77..878c2cd5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,7 @@ *.iml .gradle /local.properties -/.idea/caches -/.idea/libraries -/.idea/modules.xml -/.idea/workspace.xml -/.idea/navEditor.xml -/.idea/assetWizardSettings.xml +.idea .DS_Store /build /captures diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 26d33521..00000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index bb0255c2..00000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -VibeYou \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index 7643783a..00000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index 79ee123c..00000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index b589d56e..00000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml deleted file mode 100644 index 0c0c3383..00000000 --- a/.idea/deploymentTargetDropDown.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index cb865f69..00000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 44ca2d9b..00000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml deleted file mode 100644 index 217e5c51..00000000 --- a/.idea/kotlinc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/ktlint.xml b/.idea/ktlint.xml deleted file mode 100644 index e1ecd151..00000000 --- a/.idea/ktlint.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - true - false - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 8978d23d..00000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7f..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2efb2471..13f4346d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,8 +1,9 @@ plugins { id("com.android.application") kotlin("android") + id("kotlin-parcelize") id("com.google.devtools.ksp") - id("org.jetbrains.kotlin.plugin.serialization") version "1.8.21" + id("org.jetbrains.kotlin.plugin.serialization") version "1.9.23" } android { @@ -13,8 +14,8 @@ android { applicationId = "app.suhasdissa.vibeyou" minSdk = 24 targetSdk = 34 - versionCode = 3 - versionName = "3.0" + versionCode = 5 + versionName = "4.1" ksp { arg("room.schemaLocation", "$projectDir/schemas") @@ -50,7 +51,7 @@ android { compose = true } composeOptions { - kotlinCompilerExtensionVersion = "1.4.7" + kotlinCompilerExtensionVersion = "1.5.11" } packaging { resources { @@ -61,21 +62,23 @@ android { dependencies { - implementation("androidx.core:core-ktx:1.12.0") - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2") - implementation("androidx.activity:activity-compose:1.8.0") - implementation(platform("androidx.compose:compose-bom:2023.10.00")) + implementation("androidx.core:core-ktx:1.13.1") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") + implementation("androidx.activity:activity-compose:1.9.0") + implementation(platform("androidx.compose:compose-bom:2024.05.00")) implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui-graphics") implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.compose.material3:material3") - implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2") - implementation("androidx.navigation:navigation-compose:2.7.4") + implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0") + implementation("androidx.navigation:navigation-compose:2.8.0-alpha08") - implementation("androidx.compose.material:material-icons-extended:1.5.3") + implementation("androidx.compose.material:material-icons-extended:1.6.7") - implementation("io.coil-kt:coil-compose:2.4.0") + implementation("io.coil-kt:coil-compose:2.6.0") + implementation("com.google.android.material:material:1.11.0") + implementation("androidx.compose.material3.adaptive:adaptive-android:1.0.0-alpha12") val media3Version = "1.1.1" @@ -88,7 +91,7 @@ dependencies { implementation("androidx.media3:media3-session:$media3Version") - val roomVersion = "2.5.2" + val roomVersion = "2.6.1" implementation("androidx.room:room-runtime:$roomVersion") implementation("androidx.room:room-ktx:$roomVersion") @@ -97,15 +100,16 @@ dependencies { implementation("org.burnoutcrew.composereorderable:reorderable:0.9.6") implementation("com.github.nanihadesuka:LazyColumnScrollbar:1.8.0") + implementation("androidx.window:window:1.2.0") - implementation("com.squareup.retrofit2:retrofit:2.9.0") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1") + implementation("com.squareup.retrofit2:retrofit:2.11.0") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0") testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") - androidTestImplementation(platform("androidx.compose:compose-bom:2023.10.00")) + androidTestImplementation(platform("androidx.compose:compose-bom:2024.05.00")) androidTestImplementation("androidx.compose.ui:ui-test-junit4") debugImplementation("androidx.compose.ui:ui-tooling") debugImplementation("androidx.compose.ui:ui-test-manifest") diff --git a/app/schemas/app.suhasdissa.vibeyou.backend.database.SongDatabase/1.json b/app/schemas/app.suhasdissa.vibeyou.data.database.SongDatabase/1.json similarity index 100% rename from app/schemas/app.suhasdissa.vibeyou.backend.database.SongDatabase/1.json rename to app/schemas/app.suhasdissa.vibeyou.data.database.SongDatabase/1.json diff --git a/app/schemas/app.suhasdissa.vibeyou.backend.database.SongDatabase/2.json b/app/schemas/app.suhasdissa.vibeyou.data.database.SongDatabase/2.json similarity index 100% rename from app/schemas/app.suhasdissa.vibeyou.backend.database.SongDatabase/2.json rename to app/schemas/app.suhasdissa.vibeyou.data.database.SongDatabase/2.json diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6e9ba9b2..ca08cecb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -12,7 +12,9 @@ - + + android:theme="@style/Theme.VibeYou" + android:windowSoftInputMode="adjustResize"> + + + + @@ -61,6 +69,36 @@ android:scheme="https" tools:ignore="IntentFilterUniqueDataAttributes" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -72,7 +110,7 @@ diff --git a/app/src/main/java/app/suhasdissa/vibeyou/AppContainer.kt b/app/src/main/java/app/suhasdissa/vibeyou/AppContainer.kt index ce1b1c92..68da0d1c 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/AppContainer.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/AppContainer.kt @@ -2,7 +2,6 @@ package app.suhasdissa.vibeyou import android.content.ContentResolver import androidx.media3.session.MediaController -import app.suhasdissa.vibeyou.backend.database.SongDatabase import app.suhasdissa.vibeyou.backend.repository.AuthRepository import app.suhasdissa.vibeyou.backend.repository.AuthRepositoryImpl import app.suhasdissa.vibeyou.backend.repository.LocalMusicRepository @@ -10,6 +9,7 @@ import app.suhasdissa.vibeyou.backend.repository.PipedMusicRepository import app.suhasdissa.vibeyou.backend.repository.PlaylistRepository import app.suhasdissa.vibeyou.backend.repository.SongDatabaseRepository import app.suhasdissa.vibeyou.backend.repository.SongDatabaseRepositoryImpl +import app.suhasdissa.vibeyou.data.database.SongDatabase import com.google.common.util.concurrent.ListenableFuture interface AppContainer { diff --git a/app/src/main/java/app/suhasdissa/vibeyou/Destination.kt b/app/src/main/java/app/suhasdissa/vibeyou/Destination.kt deleted file mode 100644 index 4d19ea8e..00000000 --- a/app/src/main/java/app/suhasdissa/vibeyou/Destination.kt +++ /dev/null @@ -1,18 +0,0 @@ -package app.suhasdissa.vibeyou - -sealed class Destination(val route: String) { - object PipedMusic : Destination("piped_music") - object LocalMusic : Destination("local_music") - object OnlineSearch : Destination("online_search") - object LocalSearch : Destination("local_search") - object Settings : Destination("settings") - object About : Destination("about") - object NetworkSettings : Destination("net_settings") - object DatabaseSettings : Destination("database_settings") - object AppearanceSettings : Destination("appearance_settings") - object Playlists : Destination("playlist_screen") - object LocalPlaylists : Destination("local_playlist_screen") - object SavedPlaylists : Destination("saved_playlist_screen") - object Artist : Destination("artist") - object LocalArtist : Destination("local_artist") -} diff --git a/app/src/main/java/app/suhasdissa/vibeyou/MainActivity.kt b/app/src/main/java/app/suhasdissa/vibeyou/MainActivity.kt index 9032314a..2e1a07d7 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/MainActivity.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/MainActivity.kt @@ -7,69 +7,55 @@ import android.util.Log import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge import androidx.activity.viewModels -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.DrawerValue +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalNavigationDrawer import androidx.compose.material3.Surface -import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.LaunchedEffect -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.Modifier import androidx.compose.ui.graphics.toArgb import androidx.core.net.toUri -import androidx.navigation.compose.rememberNavController -import app.suhasdissa.vibeyou.backend.viewmodel.PlayerViewModel -import app.suhasdissa.vibeyou.ui.components.NavDrawerContent -import app.suhasdissa.vibeyou.ui.theme.LibreMusicTheme -import kotlinx.coroutines.launch +import androidx.lifecycle.viewmodel.compose.viewModel +import app.suhasdissa.vibeyou.presentation.layout.MainAppContent +import app.suhasdissa.vibeyou.presentation.screens.player.model.PlayerViewModel +import app.suhasdissa.vibeyou.presentation.screens.settings.model.SettingsModel +import app.suhasdissa.vibeyou.ui.theme.VibeYouTheme +import app.suhasdissa.vibeyou.utils.ThemeUtil class MainActivity : ComponentActivity() { private val playerViewModel: PlayerViewModel by viewModels { PlayerViewModel.Factory } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + enableEdgeToEdge() setContent { - LibreMusicTheme { - val navHostController = rememberNavController() + val settingsModel: SettingsModel = viewModel(factory = SettingsModel.Factory) + val playerViewModel: PlayerViewModel = + viewModel(factory = PlayerViewModel.Factory) + + val darkTheme = when (settingsModel.themeMode) { + SettingsModel.Theme.SYSTEM -> isSystemInDarkTheme() + SettingsModel.Theme.DARK, SettingsModel.Theme.AMOLED -> true + else -> false + } + VibeYouTheme( + darkTheme = darkTheme, + customColorScheme = ThemeUtil.getSchemeFromSeed( + settingsModel.customColor, + darkTheme + ), + dynamicColor = settingsModel.colorTheme == SettingsModel.ColorTheme.SYSTEM, + amoledDark = settingsModel.themeMode == SettingsModel.Theme.AMOLED + ) { val primaryColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.5f).toArgb() LaunchedEffect(Unit) { (application as MellowMusicApplication).accentColor = primaryColor } - - Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.surface - ) { - val drawerState = rememberDrawerState(DrawerValue.Closed) - val scope = rememberCoroutineScope() - var currentDestination by remember { - mutableStateOf(Destination.PipedMusic) - } - ModalNavigationDrawer( - drawerState = drawerState, - gesturesEnabled = drawerState.isOpen, - drawerContent = { - NavDrawerContent( - currentDestination = currentDestination, - onDestinationSelected = { - scope.launch { - drawerState.close() - } - navHostController.navigateTo(it.route) - currentDestination = it - } - ) - } - ) { - AppNavHost(navHostController = navHostController) - } + Surface { + MainAppContent( + playerViewModel, settingsModel + ) } } } @@ -85,8 +71,11 @@ class MainActivity : ComponentActivity() { private fun handleIntent(intent: Intent) { when (intent.action) { Intent.ACTION_VIEW -> { - val uri = intent.data - uri?.let { + val uri = intent.data ?: return + // Check if uri points to a device file + if (uri.scheme == "file" || uri.scheme == "content") { + playerViewModel.tryToPlayUri(uri) + } else { processLink(uri) } } @@ -127,4 +116,4 @@ class MainActivity : ComponentActivity() { Toast.makeText(this, vidId, Toast.LENGTH_SHORT).show() playerViewModel.tryToPlayId(vidId) } -} +} \ No newline at end of file diff --git a/app/src/main/java/app/suhasdissa/vibeyou/MellowMusicApplication.kt b/app/src/main/java/app/suhasdissa/vibeyou/MellowMusicApplication.kt index d4d8e8a8..62543067 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/MellowMusicApplication.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/MellowMusicApplication.kt @@ -5,11 +5,12 @@ import android.content.ComponentName import android.graphics.Color import androidx.media3.session.MediaController import androidx.media3.session.SessionToken -import app.suhasdissa.vibeyou.backend.database.SongDatabase -import app.suhasdissa.vibeyou.backend.services.PlayerService +import app.suhasdissa.vibeyou.data.database.SongDatabase +import app.suhasdissa.vibeyou.domain.models.primary.EqualizerData import app.suhasdissa.vibeyou.utils.Pref import app.suhasdissa.vibeyou.utils.UpdateUtil import app.suhasdissa.vibeyou.utils.preferences +import app.suhasdissa.vibeyou.utils.services.PlayerService import coil.ImageLoader import coil.ImageLoaderFactory import coil.disk.DiskCache @@ -20,6 +21,11 @@ class MellowMusicApplication : Application(), ImageLoaderFactory { lateinit var container: AppContainer var accentColor: Int = Color.TRANSPARENT + /** + * Data stored here with the details of the equalizer + */ + var supportedEqualizerData: EqualizerData? = null + override fun onCreate() { super.onCreate() val sessionToken = diff --git a/app/src/main/java/app/suhasdissa/vibeyou/NavHost.kt b/app/src/main/java/app/suhasdissa/vibeyou/NavHost.kt deleted file mode 100644 index 5ddcfe5b..00000000 --- a/app/src/main/java/app/suhasdissa/vibeyou/NavHost.kt +++ /dev/null @@ -1,132 +0,0 @@ -package app.suhasdissa.vibeyou - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner -import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.navigation.NavHostController -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import app.suhasdissa.vibeyou.backend.viewmodel.LocalSearchViewModel -import app.suhasdissa.vibeyou.backend.viewmodel.LocalSongViewModel -import app.suhasdissa.vibeyou.backend.viewmodel.PipedSearchViewModel -import app.suhasdissa.vibeyou.backend.viewmodel.PlaylistViewModel -import app.suhasdissa.vibeyou.ui.screens.home.HomeScreen -import app.suhasdissa.vibeyou.ui.screens.search.AlbumScreen -import app.suhasdissa.vibeyou.ui.screens.search.ArtistScreen -import app.suhasdissa.vibeyou.ui.screens.search.LocalSearchScreen -import app.suhasdissa.vibeyou.ui.screens.search.SearchScreen -import app.suhasdissa.vibeyou.ui.screens.settings.AboutScreen -import app.suhasdissa.vibeyou.ui.screens.settings.AppearanceSettingsScreen -import app.suhasdissa.vibeyou.ui.screens.settings.DatabaseSettingsScreen -import app.suhasdissa.vibeyou.ui.screens.settings.NetworkSettingsScreen -import app.suhasdissa.vibeyou.ui.screens.settings.SettingsScreen - -@Composable -fun AppNavHost(navHostController: NavHostController) { - val viewModelStoreOwner = LocalViewModelStoreOwner.current!! - NavHost( - navController = navHostController, - startDestination = Destination.PipedMusic.route - ) { - composable(route = Destination.PipedMusic.route) { - CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) { - HomeScreen(onNavigate = { destination -> - navHostController.navigateTo(destination.route) - }) - } - } - - composable( - route = Destination.OnlineSearch.route - ) { - CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) { - SearchScreen(onNavigate = { - navHostController.navigateTo(it.route) - }) - } - } - composable( - route = Destination.LocalSearch.route - ) { - CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) { - LocalSearchScreen(onNavigate = { - navHostController.navigateTo(it.route) - }) - } - } - - composable(route = Destination.Settings.route) { - SettingsScreen(onNavigate = { route -> - navHostController.navigateTo(route) - }) - } - - composable(route = Destination.About.route) { - AboutScreen() - } - - composable(route = Destination.NetworkSettings.route) { - NetworkSettingsScreen() - } - - composable(route = Destination.DatabaseSettings.route) { - DatabaseSettingsScreen() - } - - composable(route = Destination.AppearanceSettings.route) { - AppearanceSettingsScreen() - } - - composable(Destination.Playlists.route) { - CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) { - val searchViewModel: PipedSearchViewModel = - viewModel(factory = PipedSearchViewModel.Factory) - AlbumScreen(searchViewModel.albumInfoState) - } - } - - composable(Destination.LocalPlaylists.route) { - CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) { - val searchViewModel: LocalSearchViewModel = - viewModel(factory = LocalSongViewModel.Factory) - AlbumScreen(searchViewModel.albumInfoState) - } - } - - composable(Destination.SavedPlaylists.route) { - CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) { - val searchViewModel: PlaylistViewModel = - viewModel(factory = PlaylistViewModel.Factory) - AlbumScreen(searchViewModel.albumInfoState) - } - } - - composable(route = Destination.Artist.route) { - CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) { - val searchViewModel: PipedSearchViewModel = - viewModel(factory = PipedSearchViewModel.Factory) - ArtistScreen(onClickAlbum = { - searchViewModel.getPlaylistInfo(it) - navHostController.navigateTo(Destination.Playlists.route) - }, searchViewModel.artistInfoState) - } - } - - composable(route = Destination.LocalArtist.route) { - CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) { - val searchViewModel: LocalSearchViewModel = - viewModel(factory = LocalSearchViewModel.Factory) - ArtistScreen(onClickAlbum = { - searchViewModel.getAlbumInfo(it) - navHostController.navigateTo(Destination.LocalPlaylists.route) - }, searchViewModel.artistInfoState) - } - } - } -} - -fun NavHostController.navigateTo(route: String) = this.navigate(route) { - launchSingleTop = true - restoreState = true -} diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/data/Artist.kt b/app/src/main/java/app/suhasdissa/vibeyou/backend/data/Artist.kt deleted file mode 100644 index 6e4e9571..00000000 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/data/Artist.kt +++ /dev/null @@ -1,12 +0,0 @@ -package app.suhasdissa.vibeyou.backend.data - -import android.net.Uri - -data class Artist( - val id: String, - val artistsText: String, - val thumbnailUri: Uri? = null, - val description: String? = null, - val numberOfTracks: Int? = null, - val numberOfAlbums: Int? = null -) diff --git a/app/src/main/java/app/suhasdissa/vibeyou/data/api/HyperpipeApi.kt b/app/src/main/java/app/suhasdissa/vibeyou/data/api/HyperpipeApi.kt new file mode 100644 index 00000000..f09ec1cb --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/data/api/HyperpipeApi.kt @@ -0,0 +1,13 @@ +package app.suhasdissa.vibeyou.data.api + +import app.suhasdissa.vibeyou.backend.models.hyper.NextSongsResponse +import retrofit2.http.GET +import retrofit2.http.Path + +interface HyperpipeApi { + @GET("https://{instance}/next/{videoId}") + suspend fun getNext( + @Path("instance") instance: String, + @Path("videoId") videoId: String + ): NextSongsResponse +} \ No newline at end of file diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/api/PipedApi.kt b/app/src/main/java/app/suhasdissa/vibeyou/data/api/PipedApi.kt similarity index 98% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/api/PipedApi.kt rename to app/src/main/java/app/suhasdissa/vibeyou/data/api/PipedApi.kt index bdd4bed8..5a9aa9bd 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/api/PipedApi.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/data/api/PipedApi.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.backend.api +package app.suhasdissa.vibeyou.data.api import app.suhasdissa.vibeyou.backend.models.Login import app.suhasdissa.vibeyou.backend.models.PipedInstance diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/database/SongDatabase.kt b/app/src/main/java/app/suhasdissa/vibeyou/data/database/SongDatabase.kt similarity index 68% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/database/SongDatabase.kt rename to app/src/main/java/app/suhasdissa/vibeyou/data/database/SongDatabase.kt index 701809de..6080a63b 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/database/SongDatabase.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/data/database/SongDatabase.kt @@ -1,18 +1,18 @@ -package app.suhasdissa.vibeyou.backend.database +package app.suhasdissa.vibeyou.data.database import android.content.Context import androidx.room.AutoMigration import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase -import app.suhasdissa.vibeyou.backend.database.dao.PlaylistDao -import app.suhasdissa.vibeyou.backend.database.dao.RawDao -import app.suhasdissa.vibeyou.backend.database.dao.SearchDao -import app.suhasdissa.vibeyou.backend.database.dao.SongsDao -import app.suhasdissa.vibeyou.backend.database.entities.PlaylistEntity -import app.suhasdissa.vibeyou.backend.database.entities.SearchQuery -import app.suhasdissa.vibeyou.backend.database.entities.SongEntity -import app.suhasdissa.vibeyou.backend.database.entities.SongPlaylistMap +import app.suhasdissa.vibeyou.data.database.dao.PlaylistDao +import app.suhasdissa.vibeyou.data.database.dao.RawDao +import app.suhasdissa.vibeyou.data.database.dao.SearchDao +import app.suhasdissa.vibeyou.data.database.dao.SongsDao +import app.suhasdissa.vibeyou.data.database.entities.PlaylistEntity +import app.suhasdissa.vibeyou.data.database.entities.SearchQuery +import app.suhasdissa.vibeyou.data.database.entities.SongEntity +import app.suhasdissa.vibeyou.data.database.entities.SongPlaylistMap @Database( entities = [ diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/database/dao/PlaylistDao.kt b/app/src/main/java/app/suhasdissa/vibeyou/data/database/dao/PlaylistDao.kt similarity index 77% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/database/dao/PlaylistDao.kt rename to app/src/main/java/app/suhasdissa/vibeyou/data/database/dao/PlaylistDao.kt index a41ddaba..c56f8de0 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/database/dao/PlaylistDao.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/data/database/dao/PlaylistDao.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.backend.database.dao +package app.suhasdissa.vibeyou.data.database.dao import androidx.room.Dao import androidx.room.Delete @@ -6,9 +6,9 @@ import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query import androidx.room.Transaction -import app.suhasdissa.vibeyou.backend.database.entities.PlaylistEntity -import app.suhasdissa.vibeyou.backend.database.entities.PlaylistWithSongs -import app.suhasdissa.vibeyou.backend.database.entities.SongPlaylistMap +import app.suhasdissa.vibeyou.data.database.entities.PlaylistEntity +import app.suhasdissa.vibeyou.data.database.entities.PlaylistWithSongs +import app.suhasdissa.vibeyou.data.database.entities.SongPlaylistMap import kotlinx.coroutines.flow.Flow @Dao diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/database/dao/RawDao.kt b/app/src/main/java/app/suhasdissa/vibeyou/data/database/dao/RawDao.kt similarity index 79% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/database/dao/RawDao.kt rename to app/src/main/java/app/suhasdissa/vibeyou/data/database/dao/RawDao.kt index 66643f90..9ac25614 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/database/dao/RawDao.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/data/database/dao/RawDao.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.backend.database.dao +package app.suhasdissa.vibeyou.data.database.dao import androidx.room.Dao import androidx.room.RawQuery diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/database/dao/SearchDao.kt b/app/src/main/java/app/suhasdissa/vibeyou/data/database/dao/SearchDao.kt similarity index 81% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/database/dao/SearchDao.kt rename to app/src/main/java/app/suhasdissa/vibeyou/data/database/dao/SearchDao.kt index 8c3e938b..348e1a83 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/database/dao/SearchDao.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/data/database/dao/SearchDao.kt @@ -1,10 +1,10 @@ -package app.suhasdissa.vibeyou.backend.database.dao +package app.suhasdissa.vibeyou.data.database.dao import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query -import app.suhasdissa.vibeyou.backend.database.entities.SearchQuery +import app.suhasdissa.vibeyou.data.database.entities.SearchQuery @Dao interface SearchDao { diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/database/dao/SongsDao.kt b/app/src/main/java/app/suhasdissa/vibeyou/data/database/dao/SongsDao.kt similarity index 92% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/database/dao/SongsDao.kt rename to app/src/main/java/app/suhasdissa/vibeyou/data/database/dao/SongsDao.kt index 975f0651..6ad27749 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/database/dao/SongsDao.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/data/database/dao/SongsDao.kt @@ -1,11 +1,11 @@ -package app.suhasdissa.vibeyou.backend.database.dao +package app.suhasdissa.vibeyou.data.database.dao import androidx.room.Dao import androidx.room.Delete import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query -import app.suhasdissa.vibeyou.backend.database.entities.SongEntity +import app.suhasdissa.vibeyou.data.database.entities.SongEntity import kotlinx.coroutines.flow.Flow @Dao diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/database/entities/PlaylistEntity.kt b/app/src/main/java/app/suhasdissa/vibeyou/data/database/entities/PlaylistEntity.kt similarity index 71% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/database/entities/PlaylistEntity.kt rename to app/src/main/java/app/suhasdissa/vibeyou/data/database/entities/PlaylistEntity.kt index f90216f7..4d70865d 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/database/entities/PlaylistEntity.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/data/database/entities/PlaylistEntity.kt @@ -1,8 +1,8 @@ -package app.suhasdissa.vibeyou.backend.database.entities +package app.suhasdissa.vibeyou.data.database.entities import androidx.room.Entity import androidx.room.PrimaryKey -import app.suhasdissa.vibeyou.backend.data.Album +import app.suhasdissa.vibeyou.domain.models.primary.Album @Entity(tableName = "playlists") data class PlaylistEntity( diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/database/entities/PlaylistWithSongs.kt b/app/src/main/java/app/suhasdissa/vibeyou/data/database/entities/PlaylistWithSongs.kt similarity index 89% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/database/entities/PlaylistWithSongs.kt rename to app/src/main/java/app/suhasdissa/vibeyou/data/database/entities/PlaylistWithSongs.kt index 5dca8d5c..7f402b89 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/database/entities/PlaylistWithSongs.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/data/database/entities/PlaylistWithSongs.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.backend.database.entities +package app.suhasdissa.vibeyou.data.database.entities import androidx.room.Embedded import androidx.room.Junction diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/database/entities/SearchQuery.kt b/app/src/main/java/app/suhasdissa/vibeyou/data/database/entities/SearchQuery.kt similarity index 84% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/database/entities/SearchQuery.kt rename to app/src/main/java/app/suhasdissa/vibeyou/data/database/entities/SearchQuery.kt index 0ea682d9..9f967710 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/database/entities/SearchQuery.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/data/database/entities/SearchQuery.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.backend.database.entities +package app.suhasdissa.vibeyou.data.database.entities import androidx.room.Entity import androidx.room.Index diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/database/entities/SongEntity.kt b/app/src/main/java/app/suhasdissa/vibeyou/data/database/entities/SongEntity.kt similarity index 88% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/database/entities/SongEntity.kt rename to app/src/main/java/app/suhasdissa/vibeyou/data/database/entities/SongEntity.kt index 1d0d2c77..20c3d10d 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/database/entities/SongEntity.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/data/database/entities/SongEntity.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.backend.database.entities +package app.suhasdissa.vibeyou.data.database.entities import androidx.room.ColumnInfo import androidx.room.Entity diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/database/entities/SongPlaylistMap.kt b/app/src/main/java/app/suhasdissa/vibeyou/data/database/entities/SongPlaylistMap.kt similarity index 92% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/database/entities/SongPlaylistMap.kt rename to app/src/main/java/app/suhasdissa/vibeyou/data/database/entities/SongPlaylistMap.kt index 50bee3e2..c848bdfc 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/database/entities/SongPlaylistMap.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/data/database/entities/SongPlaylistMap.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.backend.database.entities +package app.suhasdissa.vibeyou.data.database.entities import androidx.room.ColumnInfo import androidx.room.Entity diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/models/Login.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/Login.kt similarity index 100% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/models/Login.kt rename to app/src/main/java/app/suhasdissa/vibeyou/domain/models/Login.kt diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/models/PipedInstance.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/PipedInstance.kt similarity index 100% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/models/PipedInstance.kt rename to app/src/main/java/app/suhasdissa/vibeyou/domain/models/PipedInstance.kt diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/models/PipedVIdeoResponse.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/PipedVIdeoResponse.kt similarity index 100% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/models/PipedVIdeoResponse.kt rename to app/src/main/java/app/suhasdissa/vibeyou/domain/models/PipedVIdeoResponse.kt diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/models/PlayerRepeatMode.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/PlayerRepeatMode.kt similarity index 100% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/models/PlayerRepeatMode.kt rename to app/src/main/java/app/suhasdissa/vibeyou/domain/models/PlayerRepeatMode.kt diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/models/PlayerState.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/PlayerState.kt similarity index 100% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/models/PlayerState.kt rename to app/src/main/java/app/suhasdissa/vibeyou/domain/models/PlayerState.kt diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/models/SearchFilter.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/SearchFilter.kt similarity index 100% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/models/SearchFilter.kt rename to app/src/main/java/app/suhasdissa/vibeyou/domain/models/SearchFilter.kt diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/models/Token.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/Token.kt similarity index 100% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/models/Token.kt rename to app/src/main/java/app/suhasdissa/vibeyou/domain/models/Token.kt diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/models/artists/Artist.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/artists/Artist.kt similarity index 100% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/models/artists/Artist.kt rename to app/src/main/java/app/suhasdissa/vibeyou/domain/models/artists/Artist.kt diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/models/artists/Artists.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/artists/Artists.kt similarity index 100% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/models/artists/Artists.kt rename to app/src/main/java/app/suhasdissa/vibeyou/domain/models/artists/Artists.kt diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/models/artists/Channel.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/artists/Channel.kt similarity index 100% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/models/artists/Channel.kt rename to app/src/main/java/app/suhasdissa/vibeyou/domain/models/artists/Channel.kt diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/models/artists/ChannelTabResponse.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/artists/ChannelTabResponse.kt similarity index 100% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/models/artists/ChannelTabResponse.kt rename to app/src/main/java/app/suhasdissa/vibeyou/domain/models/artists/ChannelTabResponse.kt diff --git a/app/src/main/java/app/suhasdissa/vibeyou/domain/models/hyper/MediaSession.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/hyper/MediaSession.kt new file mode 100644 index 00000000..18e86948 --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/hyper/MediaSession.kt @@ -0,0 +1,9 @@ +package app.suhasdissa.vibeyou.backend.models.hyper + +import kotlinx.serialization.Serializable + +@Serializable +data class MediaSession( + val album: String, + val thumbnails: List +) \ No newline at end of file diff --git a/app/src/main/java/app/suhasdissa/vibeyou/domain/models/hyper/NextSongsResponse.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/hyper/NextSongsResponse.kt new file mode 100644 index 00000000..20540e87 --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/hyper/NextSongsResponse.kt @@ -0,0 +1,10 @@ +package app.suhasdissa.vibeyou.backend.models.hyper + +import kotlinx.serialization.Serializable + +@Serializable +data class NextSongsResponse( + val lyricsId: String, + val mediaSession: MediaSession, + val songs: List +) \ No newline at end of file diff --git a/app/src/main/java/app/suhasdissa/vibeyou/domain/models/hyper/Song.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/hyper/Song.kt new file mode 100644 index 00000000..b74d316f --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/hyper/Song.kt @@ -0,0 +1,36 @@ +package app.suhasdissa.vibeyou.backend.models.hyper + +import androidx.core.net.toUri +import app.suhasdissa.vibeyou.data.database.entities.SongEntity +import app.suhasdissa.vibeyou.domain.models.primary.Song +import kotlinx.serialization.Serializable + +@Serializable +data class Song( + val id: String, + val subtitle: String, + val thumbnails: List, + val title: String +) { + val asSong: Song + get() { + return Song( + id = id, + title = title, + artistsText = subtitle, + durationText = null, + thumbnailUri = thumbnails.maxByOrNull { it.height }?.url?.toUri() + ) + } + + val asSongEntity: SongEntity + get() { + return SongEntity( + id = id, + title = title, + artistsText = subtitle, + durationText = null, + thumbnailUrl = thumbnails.maxByOrNull { it.height }?.url + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/app/suhasdissa/vibeyou/domain/models/hyper/Thumbnail.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/hyper/Thumbnail.kt new file mode 100644 index 00000000..2d1fdee2 --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/hyper/Thumbnail.kt @@ -0,0 +1,10 @@ +package app.suhasdissa.vibeyou.backend.models.hyper + +import kotlinx.serialization.Serializable + +@Serializable +data class Thumbnail( + val height: Int, + val url: String, + val width: Int +) \ No newline at end of file diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/models/playlists/Playlist.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/playlists/Playlist.kt similarity index 100% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/models/playlists/Playlist.kt rename to app/src/main/java/app/suhasdissa/vibeyou/domain/models/playlists/Playlist.kt diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/models/playlists/PlaylistInfo.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/playlists/PlaylistInfo.kt similarity index 100% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/models/playlists/PlaylistInfo.kt rename to app/src/main/java/app/suhasdissa/vibeyou/domain/models/playlists/PlaylistInfo.kt diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/models/playlists/Playlists.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/playlists/Playlists.kt similarity index 100% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/models/playlists/Playlists.kt rename to app/src/main/java/app/suhasdissa/vibeyou/domain/models/playlists/Playlists.kt diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/data/Album.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/primary/Album.kt similarity index 50% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/data/Album.kt rename to app/src/main/java/app/suhasdissa/vibeyou/domain/models/primary/Album.kt index 08366798..df9cefd4 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/data/Album.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/primary/Album.kt @@ -1,17 +1,24 @@ -package app.suhasdissa.vibeyou.backend.data +package app.suhasdissa.vibeyou.domain.models.primary import android.net.Uri +import android.os.Parcelable +import app.suhasdissa.vibeyou.utils.UriSerializer +import kotlinx.parcelize.Parcelize +import kotlinx.serialization.Serializable +@Serializable +@Parcelize data class Album( val id: String, val title: String, + @Serializable(with = UriSerializer::class) val thumbnailUri: Uri? = null, val artistsText: String, val numberOfSongs: Int? = null, val isLocal: Boolean = false, val type: Type = Type.ALBUM -) { +) : Parcelable { enum class Type { PLAYLIST, ALBUM } -} +} \ No newline at end of file diff --git a/app/src/main/java/app/suhasdissa/vibeyou/domain/models/primary/Artist.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/primary/Artist.kt new file mode 100644 index 00000000..ce1ce08e --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/primary/Artist.kt @@ -0,0 +1,19 @@ +package app.suhasdissa.vibeyou.domain.models.primary + +import android.net.Uri +import android.os.Parcelable +import app.suhasdissa.vibeyou.utils.UriSerializer +import kotlinx.parcelize.Parcelize +import kotlinx.serialization.Serializable + +@Parcelize +@Serializable +data class Artist( + val id: String, + val artistsText: String, + @Serializable(with = UriSerializer::class) + val thumbnailUri: Uri? = null, + val description: String? = null, + val numberOfTracks: Int? = null, + val numberOfAlbums: Int? = null +) : Parcelable diff --git a/app/src/main/java/app/suhasdissa/vibeyou/domain/models/primary/EqualizerData.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/primary/EqualizerData.kt new file mode 100644 index 00000000..ce3c51fc --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/primary/EqualizerData.kt @@ -0,0 +1,12 @@ +package app.suhasdissa.vibeyou.domain.models.primary + +data class EqualizerBand( + val frequency: Int, + val minLevel: Short, + val maxLevel: Short, +) + +data class EqualizerData( + val presets: List, + val bands: List +) diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/data/Song.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/primary/Song.kt similarity index 82% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/data/Song.kt rename to app/src/main/java/app/suhasdissa/vibeyou/domain/models/primary/Song.kt index bd6cfa8a..0ba43688 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/data/Song.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/primary/Song.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.backend.data +package app.suhasdissa.vibeyou.domain.models.primary import android.net.Uri @@ -13,7 +13,8 @@ data class Song( val artistId: Long? = null, val isLocal: Boolean = false, val creationDate: Long? = null, - val dateAdded: Long? = null + val dateAdded: Long? = null, + val trackNumber: Long? = null ) { fun toggleLike(): Song { return copy( diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/models/songs/SongItem.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/songs/SongItem.kt similarity index 100% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/models/songs/SongItem.kt rename to app/src/main/java/app/suhasdissa/vibeyou/domain/models/songs/SongItem.kt diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/models/songs/Songs.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/models/songs/Songs.kt similarity index 100% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/models/songs/Songs.kt rename to app/src/main/java/app/suhasdissa/vibeyou/domain/models/songs/Songs.kt diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/repository/AuthRepository.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/repository/AuthRepository.kt similarity index 100% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/repository/AuthRepository.kt rename to app/src/main/java/app/suhasdissa/vibeyou/domain/repository/AuthRepository.kt diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/repository/LocalMusicRepository.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/repository/LocalMusicRepository.kt similarity index 63% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/repository/LocalMusicRepository.kt rename to app/src/main/java/app/suhasdissa/vibeyou/domain/repository/LocalMusicRepository.kt index 6fdc52e2..ad66311a 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/repository/LocalMusicRepository.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/domain/repository/LocalMusicRepository.kt @@ -5,17 +5,19 @@ import android.content.ContentResolver import android.content.ContentUris import android.net.Uri import android.os.Build +import android.os.Environment.getExternalStorageDirectory import android.provider.MediaStore import android.text.format.DateUtils import androidx.core.net.toUri -import app.suhasdissa.vibeyou.backend.data.Album -import app.suhasdissa.vibeyou.backend.data.Artist -import app.suhasdissa.vibeyou.backend.data.Song -import app.suhasdissa.vibeyou.backend.database.dao.SearchDao -import app.suhasdissa.vibeyou.backend.database.entities.SearchQuery +import app.suhasdissa.vibeyou.data.database.dao.SearchDao +import app.suhasdissa.vibeyou.data.database.entities.SearchQuery +import app.suhasdissa.vibeyou.domain.models.primary.Album +import app.suhasdissa.vibeyou.domain.models.primary.Artist +import app.suhasdissa.vibeyou.domain.models.primary.Song import app.suhasdissa.vibeyou.utils.Pref import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import java.io.File class LocalMusicRepository( private val contentResolver: ContentResolver, @@ -26,6 +28,7 @@ class LocalMusicRepository( private var artistCache = listOf() suspend fun getAllSongs(): List = withContext(Dispatchers.IO) { + if (songsCache.isNotEmpty()) return@withContext songsCache val songs = mutableListOf() @@ -47,7 +50,8 @@ class LocalMusicRepository( MediaStore.Audio.Media.ALBUM_ID, MediaStore.Audio.Media.ARTIST_ID, MediaStore.Audio.Media.DATE_MODIFIED, - MediaStore.Audio.Media.DATE_ADDED + MediaStore.Audio.Media.DATE_ADDED, + MediaStore.Audio.Media.CD_TRACK_NUMBER ) val sortOrder = "${MediaStore.Audio.Media.TITLE} ASC" @@ -73,6 +77,8 @@ class LocalMusicRepository( MediaStore.Audio.Media.DATE_MODIFIED ) val dateAddedColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATE_ADDED) + val trackNumberColumn = + cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.CD_TRACK_NUMBER) while (cursor.moveToNext()) { val id = cursor.getLong(idColumn) @@ -108,7 +114,8 @@ class LocalMusicRepository( artistId = cursor.getLong(artistIdColumn), isLocal = true, creationDate = cursor.getLong(creationDateColumn), - dateAdded = cursor.getLong(dateAddedColumn) + dateAdded = cursor.getLong(dateAddedColumn), + trackNumber = cursor.getLong(trackNumberColumn) ) ) } @@ -119,6 +126,136 @@ class LocalMusicRepository( songs } + suspend fun getSongFromUri(uri: Uri): Song? = withContext(Dispatchers.IO) { + val collection = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + MediaStore.Audio.Media.getContentUri( + MediaStore.VOLUME_EXTERNAL + ) + } else { + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + } + + val projection = arrayOf( + MediaStore.Audio.Media._ID, + MediaStore.Audio.Media.TITLE, + MediaStore.Audio.Media.DISPLAY_NAME, + MediaStore.Audio.Media.DURATION, + MediaStore.Audio.Media.ARTIST, + MediaStore.Audio.Media.ALBUM_ID, + MediaStore.Audio.Media.ARTIST_ID, + MediaStore.Audio.Media.DATE_MODIFIED, + MediaStore.Audio.Media.DATE_ADDED, + MediaStore.Audio.Media.CD_TRACK_NUMBER + ) + + val selection = "${MediaStore.Audio.Media.DATA} = ?" + + val path = getPathFromContentUri(uri) + val selectionArgs = arrayOf(path) + + val query = contentResolver.query( + collection, + projection, + selection, + selectionArgs, + null + ) + + query?.use { cursor -> + val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID) + val titleColumn = + cursor.getColumnIndex(MediaStore.Audio.Media.TITLE) + val nameColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME) + val durationColumn = + cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION) + val artistColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST) + val albumColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID) + val artistIdColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID) + val creationDateColumn = cursor.getColumnIndexOrThrow( + MediaStore.Audio.Media.DATE_MODIFIED + ) + val dateAddedColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATE_ADDED) + val trackNumberColumn = + cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.CD_TRACK_NUMBER) + + if (cursor.moveToFirst()) { + val id = cursor.getLong(idColumn) + val name = + if (cursor.isNull(titleColumn)) { + cursor.getString(nameColumn) + } else { + cursor.getString( + titleColumn + ) + } + val albumId = cursor.getLong(albumColumn) + + val contentUri: Uri = ContentUris.withAppendedId( + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, + id + ) + + val duration = cursor.getLong(durationColumn) / 1000 + if (duration == 0L) return@withContext null + + return@withContext Song( + id = contentUri.toString(), + title = name, + durationText = DateUtils.formatElapsedTime(duration), + thumbnailUri = getAlbumArt(albumId), + artistsText = cursor.getString(artistColumn), + albumId = albumId, + artistId = cursor.getLong(artistIdColumn), + isLocal = true, + creationDate = cursor.getLong(creationDateColumn), + dateAdded = cursor.getLong(dateAddedColumn), + trackNumber = cursor.getLong(trackNumberColumn) + ) + } + } + return@withContext null + } + + private fun getPathFromContentUri(uri: Uri): String? { + var songFile: File? = null + if (uri.authority == "com.android.externalstorage.documents") { + val path = uri.path?.split(":".toRegex(), 2)?.get(1) + if (path != null) { + songFile = File(getExternalStorageDirectory(), path) + } + } + if (songFile == null) { + val path = getFilePathFromUri(uri) + if (path != null) + songFile = File(path) + } + if (songFile == null && uri.path != null) { + songFile = File(uri.path!!) + } + if (songFile != null) { + return songFile.absolutePath + } + return null + } + + private fun getFilePathFromUri(uri: Uri): String? { + val column = MediaStore.Audio.Media.DATA + val projection = arrayOf(column) + val query = contentResolver.query(uri, projection, null, null, null) + + query?.use { cursor -> + try { + if (cursor.moveToFirst()) { + val columnIndex = cursor.getColumnIndexOrThrow(column) + return cursor.getString(columnIndex) + } + } catch (e: Exception) { + e.printStackTrace() + } + } + return null + } + suspend fun getAllAlbums(): List = withContext(Dispatchers.IO) { if (albumCache.isNotEmpty()) return@withContext albumCache @@ -258,6 +395,7 @@ class LocalMusicRepository( suspend fun getAlbumInfo(albumId: Long): List { return getAllSongs() .filter { it.albumId == albumId } + .sortedBy { it.trackNumber } } suspend fun getArtistInfo(artistText: String): List { @@ -275,6 +413,7 @@ class LocalMusicRepository( searchDao.addSearchQuery(SearchQuery(id = 0, query)) } + fun deleteQuery(query: String) = searchDao.deleteQuery(query) fun getSearchHistory() = searchDao.getSearchHistory() diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/repository/PipedMusicRepository.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/repository/PipedMusicRepository.kt similarity index 77% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/repository/PipedMusicRepository.kt rename to app/src/main/java/app/suhasdissa/vibeyou/domain/repository/PipedMusicRepository.kt index 2e216ff4..351c764b 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/repository/PipedMusicRepository.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/domain/repository/PipedMusicRepository.kt @@ -3,16 +3,16 @@ package app.suhasdissa.vibeyou.backend.repository import android.net.Uri import android.util.Log import androidx.core.net.toUri -import app.suhasdissa.vibeyou.backend.data.Album -import app.suhasdissa.vibeyou.backend.data.Artist -import app.suhasdissa.vibeyou.backend.data.Song -import app.suhasdissa.vibeyou.backend.database.dao.SearchDao -import app.suhasdissa.vibeyou.backend.database.dao.SongsDao -import app.suhasdissa.vibeyou.backend.database.entities.SearchQuery import app.suhasdissa.vibeyou.backend.models.SearchFilter import app.suhasdissa.vibeyou.backend.models.artists.Channel import app.suhasdissa.vibeyou.backend.models.artists.ChannelTab import app.suhasdissa.vibeyou.backend.models.playlists.PlaylistInfo +import app.suhasdissa.vibeyou.data.database.dao.SearchDao +import app.suhasdissa.vibeyou.data.database.dao.SongsDao +import app.suhasdissa.vibeyou.data.database.entities.SearchQuery +import app.suhasdissa.vibeyou.domain.models.primary.Album +import app.suhasdissa.vibeyou.domain.models.primary.Artist +import app.suhasdissa.vibeyou.domain.models.primary.Song import app.suhasdissa.vibeyou.utils.Pref import app.suhasdissa.vibeyou.utils.RetrofitHelper import app.suhasdissa.vibeyou.utils.asAlbum @@ -25,24 +25,24 @@ class PipedMusicRepository( private val searchDao: SearchDao ) { var pipedApi = RetrofitHelper.createPipedApi() + private val hyperApi = RetrofitHelper.createHyperpipeApi() - suspend fun getAudioSource(id: String): Uri? { - return runCatching { pipedApi.getStreams(vidId = id) } - .getOrNull() - ?.audioStreams - ?.get(1) - ?.url - ?.toUri() + suspend fun getAudioSource(id: String): Result { + return kotlin.runCatching { + pipedApi.getStreams(vidId = id) + .audioStreams + .getOrNull(1) + ?.url + ?.toUri() + } + } + + suspend fun getRecommendedSongs(id: String, limit: Int = 3): List { + val instance = Pref.hyperInstance ?: return emptyList() + val relatedSongs = hyperApi.getNext(instance, videoId = id).songs.subList(1, limit + 1) + songsDao.addSongs(relatedSongs.map { it.asSongEntity }) + return relatedSongs.map { it.asSong } } -// -// suspend fun getRecommendedSongs(id: String): List { -// val relatedSongs = -// pipedApi.getStreams(vidId = id).relatedStreams.slice(0..1).map { -// it.asSong -// } -// songsDao.addSongs(relatedSongs) -// return relatedSongs.map { it.asMediaItem } -// } suspend fun getPlaylistInfo(playlistId: String): PlaylistInfo = pipedApi.getPlaylistInfo(playlistId = playlistId) @@ -117,6 +117,7 @@ class PipedMusicRepository( searchDao.addSearchQuery(SearchQuery(id = 0, query)) } + fun deleteQuery(query: String) = searchDao.deleteQuery(query) fun getSearchHistory() = searchDao.getSearchHistory() } diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/repository/PlaylistRepository.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/repository/PlaylistRepository.kt similarity index 79% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/repository/PlaylistRepository.kt rename to app/src/main/java/app/suhasdissa/vibeyou/domain/repository/PlaylistRepository.kt index 8fa9b14c..4fdd83aa 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/repository/PlaylistRepository.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/domain/repository/PlaylistRepository.kt @@ -1,12 +1,12 @@ package app.suhasdissa.vibeyou.backend.repository -import app.suhasdissa.vibeyou.backend.data.Album -import app.suhasdissa.vibeyou.backend.data.Song -import app.suhasdissa.vibeyou.backend.database.dao.PlaylistDao -import app.suhasdissa.vibeyou.backend.database.dao.SongsDao -import app.suhasdissa.vibeyou.backend.database.entities.PlaylistEntity -import app.suhasdissa.vibeyou.backend.database.entities.PlaylistWithSongs -import app.suhasdissa.vibeyou.backend.database.entities.SongPlaylistMap +import app.suhasdissa.vibeyou.data.database.dao.PlaylistDao +import app.suhasdissa.vibeyou.data.database.dao.SongsDao +import app.suhasdissa.vibeyou.data.database.entities.PlaylistEntity +import app.suhasdissa.vibeyou.data.database.entities.PlaylistWithSongs +import app.suhasdissa.vibeyou.data.database.entities.SongPlaylistMap +import app.suhasdissa.vibeyou.domain.models.primary.Album +import app.suhasdissa.vibeyou.domain.models.primary.Song import app.suhasdissa.vibeyou.utils.asPlaylistEntity import app.suhasdissa.vibeyou.utils.asSongEntity import kotlinx.coroutines.Dispatchers diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/repository/SongDatabaseRepository.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/repository/SongDatabaseRepository.kt similarity index 76% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/repository/SongDatabaseRepository.kt rename to app/src/main/java/app/suhasdissa/vibeyou/domain/repository/SongDatabaseRepository.kt index 866d3f89..0af853ee 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/repository/SongDatabaseRepository.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/domain/repository/SongDatabaseRepository.kt @@ -1,7 +1,7 @@ package app.suhasdissa.vibeyou.backend.repository -import app.suhasdissa.vibeyou.backend.data.Song -import app.suhasdissa.vibeyou.backend.database.entities.SongEntity +import app.suhasdissa.vibeyou.data.database.entities.SongEntity +import app.suhasdissa.vibeyou.domain.models.primary.Song import kotlinx.coroutines.flow.Flow interface SongDatabaseRepository { diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/repository/SongDatabaseRepositoryImpl.kt b/app/src/main/java/app/suhasdissa/vibeyou/domain/repository/SongDatabaseRepositoryImpl.kt similarity index 82% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/repository/SongDatabaseRepositoryImpl.kt rename to app/src/main/java/app/suhasdissa/vibeyou/domain/repository/SongDatabaseRepositoryImpl.kt index 23b5a405..4e5881d8 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/repository/SongDatabaseRepositoryImpl.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/domain/repository/SongDatabaseRepositoryImpl.kt @@ -1,8 +1,8 @@ package app.suhasdissa.vibeyou.backend.repository -import app.suhasdissa.vibeyou.backend.data.Song -import app.suhasdissa.vibeyou.backend.database.dao.SongsDao -import app.suhasdissa.vibeyou.backend.database.entities.SongEntity +import app.suhasdissa.vibeyou.data.database.dao.SongsDao +import app.suhasdissa.vibeyou.data.database.entities.SongEntity +import app.suhasdissa.vibeyou.domain.models.primary.Song import app.suhasdissa.vibeyou.utils.asSong import app.suhasdissa.vibeyou.utils.asSongEntity import kotlinx.coroutines.flow.Flow diff --git a/app/src/main/java/app/suhasdissa/vibeyou/navigation/CustomNavTypes.kt b/app/src/main/java/app/suhasdissa/vibeyou/navigation/CustomNavTypes.kt new file mode 100644 index 00000000..b85494ab --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/navigation/CustomNavTypes.kt @@ -0,0 +1,50 @@ +package app.suhasdissa.vibeyou.navigation + +import android.os.Bundle +import androidx.navigation.NavType +import app.suhasdissa.vibeyou.domain.models.primary.Album +import app.suhasdissa.vibeyou.domain.models.primary.Artist +import kotlinx.serialization.json.Json +import java.net.URLDecoder +import java.net.URLEncoder + +val AlbumType = object : NavType( + isNullableAllowed = false +) { + override fun put(bundle: Bundle, key: String, value: Album) { + bundle.putParcelable(key, value) + } + + override fun get(bundle: Bundle, key: String): Album? { + return bundle.getParcelable(key) + } + + override fun parseValue(value: String): Album { + return Json.decodeFromString(URLDecoder.decode(value, "UTF-8")) + } + + override fun serializeAsValue(value: Album): String { + return URLEncoder.encode(Json.encodeToString(Album.serializer(), value), "UTF-8") + } +} + +val ArtistType = object : NavType( + isNullableAllowed = false +) { + override fun put(bundle: Bundle, key: String, value: Artist) { + bundle.putParcelable(key, value) + } + + override fun get(bundle: Bundle, key: String): Artist? { + return bundle.getParcelable(key) + } + + override fun parseValue(value: String): Artist { + return Json.decodeFromString(URLDecoder.decode(value, "UTF-8")) + } + + override fun serializeAsValue(value: Artist): String { + return URLEncoder.encode(Json.encodeToString(Artist.serializer(), value), "UTF-8") + } + +} \ No newline at end of file diff --git a/app/src/main/java/app/suhasdissa/vibeyou/navigation/Destination.kt b/app/src/main/java/app/suhasdissa/vibeyou/navigation/Destination.kt new file mode 100644 index 00000000..67f0b386 --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/navigation/Destination.kt @@ -0,0 +1,50 @@ +package app.suhasdissa.vibeyou.navigation + +import app.suhasdissa.vibeyou.domain.models.primary.Album +import app.suhasdissa.vibeyou.domain.models.primary.Artist +import kotlinx.serialization.Serializable + +@Serializable +sealed class Destination { + @Serializable + object OnlineMusic : Destination() + + @Serializable + object LocalMusic : Destination() + + @Serializable + object OnlineSearch : Destination() + + @Serializable + object LocalSearch : Destination() + + @Serializable + object Settings : Destination() + + @Serializable + object About : Destination() + + @Serializable + object NetworkSettings : Destination() + + @Serializable + object DatabaseSettings : Destination() + + @Serializable + object AppearanceSettings : Destination() + + @Serializable + data class Playlists(val album: Album) : Destination() + + @Serializable + data class LocalPlaylists(val album: Album) : Destination() + + @Serializable + data class SavedPlaylists(val album: Album) : Destination() + + @Serializable + data class OnlineArtist(val artist: Artist) : Destination() + + @Serializable + data class LocalArtist(val artist: Artist) : Destination() +} diff --git a/app/src/main/java/app/suhasdissa/vibeyou/navigation/NavHost.kt b/app/src/main/java/app/suhasdissa/vibeyou/navigation/NavHost.kt new file mode 100644 index 00000000..d62efa67 --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/navigation/NavHost.kt @@ -0,0 +1,351 @@ +package app.suhasdissa.vibeyou.navigation + +import androidx.compose.animation.AnimatedContentTransitionScope +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavDestination.Companion.hasRoute +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import app.suhasdissa.vibeyou.domain.models.primary.Album +import app.suhasdissa.vibeyou.domain.models.primary.Artist +import app.suhasdissa.vibeyou.presentation.screens.album.AlbumScreen +import app.suhasdissa.vibeyou.presentation.screens.album.model.LocalPlaylistViewModel +import app.suhasdissa.vibeyou.presentation.screens.album.model.OnlinePlaylistViewModel +import app.suhasdissa.vibeyou.presentation.screens.artist.ArtistScreen +import app.suhasdissa.vibeyou.presentation.screens.artist.model.LocalArtistViewModel +import app.suhasdissa.vibeyou.presentation.screens.artist.model.OnlineArtistViewModel +import app.suhasdissa.vibeyou.presentation.screens.localmusic.LocalMusicScreen +import app.suhasdissa.vibeyou.presentation.screens.localsearch.LocalSearchScreen +import app.suhasdissa.vibeyou.presentation.screens.onlinemusic.OnlineMusicScreen +import app.suhasdissa.vibeyou.presentation.screens.onlinesearch.SearchScreen +import app.suhasdissa.vibeyou.presentation.screens.player.model.PlayerViewModel +import app.suhasdissa.vibeyou.presentation.screens.playlists.model.PlaylistInfoViewModel +import app.suhasdissa.vibeyou.presentation.screens.settings.AboutScreen +import app.suhasdissa.vibeyou.presentation.screens.settings.AppearanceSettingsScreen +import app.suhasdissa.vibeyou.presentation.screens.settings.DatabaseSettingsScreen +import app.suhasdissa.vibeyou.presentation.screens.settings.NetworkSettingsScreen +import app.suhasdissa.vibeyou.presentation.screens.settings.SettingsScreen +import app.suhasdissa.vibeyou.presentation.screens.settings.model.SettingsModel +import kotlin.reflect.typeOf + +@Composable +fun AppNavHost( + modifier: Modifier = Modifier, + navHostController: NavHostController, + onDrawerOpen: (() -> Unit)?, + playerViewModel: PlayerViewModel, + settingsModel: SettingsModel +) { + NavHost( + modifier = modifier, + navController = navHostController, + startDestination = Destination.LocalMusic + ) { + composable( + enterTransition = { + if (listOf( + Destination.OnlineMusic::class, + Destination.Settings::class + ).any { initialState.destination.hasRoute(it) } + ) { + fadeIn() + } else { + scaleIn(initialScale = 3f / 4) + fadeIn() + } + }, + exitTransition = { + if (listOf( + Destination.OnlineMusic::class, + Destination.Settings::class, + Destination.OnlineSearch::class, + Destination.LocalSearch::class + ) + .any { targetState.destination.hasRoute(it) } + ) { + fadeOut() + } else { + scaleOut(targetScale = 0f) + fadeOut() + } + } + ) { + LocalMusicScreen(onNavigate = { + navHostController.navigate(it) + }, playerViewModel = playerViewModel, onDrawerOpen = onDrawerOpen) + } + + composable( + enterTransition = { + if (listOf( + Destination.LocalMusic::class, + Destination.Settings::class + ).any { initialState.destination.hasRoute(it) } + ) { + fadeIn() + } else { + scaleIn(initialScale = 3f / 4) + fadeIn() + } + }, + exitTransition = { + if (listOf( + Destination.LocalMusic::class, + Destination.Settings::class, + Destination.OnlineSearch::class, + Destination.LocalSearch::class + ) + .any { targetState.destination.hasRoute(it) } + ) { + fadeOut() + } else { + scaleOut(targetScale = 0f) + fadeOut() + } + } + ) { + OnlineMusicScreen(onNavigate = { + navHostController.navigate(it) + }, playerViewModel = playerViewModel, onDrawerOpen = onDrawerOpen) + } + + + composable( + enterTransition = { + fadeIn() + }, + exitTransition = { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Down, + targetOffset = { it }) + fadeOut() + } + ) { + SearchScreen(onNavigate = { + navHostController.navigate(it) + }, playerViewModel) + } + composable( + enterTransition = { + fadeIn() + }, + exitTransition = { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Down, + targetOffset = { it }) + fadeOut() + } + ) { + LocalSearchScreen(onNavigate = { + navHostController.navigate(it) + }, playerViewModel) + } + + composable( + enterTransition = { + if (listOf( + Destination.OnlineMusic::class, + Destination.LocalMusic::class + ).any { initialState.destination.hasRoute(it) } + ) { + fadeIn() + } else if (listOf( + Destination.About::class, + Destination.NetworkSettings::class, + Destination.DatabaseSettings::class, + Destination.AppearanceSettings::class + ) + .any { initialState.destination.hasRoute(it) } + ) { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Right, + initialOffset = { it }) + fadeIn() + } else { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Up, + initialOffset = { it / 4 }) + scaleIn(initialScale = 3f / 4) + fadeIn() + } + }, + exitTransition = { + if (listOf( + Destination.OnlineMusic::class, + Destination.LocalMusic::class + ).any { targetState.destination.hasRoute(it) } + ) { + fadeOut() + } else if (listOf( + Destination.About::class, + Destination.NetworkSettings::class, + Destination.DatabaseSettings::class, + Destination.AppearanceSettings::class + ) + .any { targetState.destination.hasRoute(it) } + ) { + slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Left, + targetOffset = { it / 4 }) + fadeOut() + } else { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Down, + targetOffset = { it / 4 }) + scaleOut(targetScale = 3f / 4) + fadeOut() + } + }) { + SettingsScreen(onNavigate = { route -> + navHostController.navigate(route) + }, onDrawerOpen = onDrawerOpen) + } + + composable( + enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Left, + initialOffset = { it }) + fadeIn() + }, + exitTransition = { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Right, + targetOffset = { it }) + fadeOut() + } + ) { + AboutScreen() + } + + composable( + enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Left, + initialOffset = { it }) + fadeIn() + }, + exitTransition = { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Right, + targetOffset = { it }) + fadeOut() + } + ) { + NetworkSettingsScreen() + } + + composable( + enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Left, + initialOffset = { it }) + fadeIn() + }, + exitTransition = { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Right, + targetOffset = { it }) + fadeOut() + } + ) { + DatabaseSettingsScreen() + } + + composable( + enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Left, + initialOffset = { it }) + fadeIn() + }, + exitTransition = { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Right, + targetOffset = { it }) + fadeOut() + } + ) { + AppearanceSettingsScreen(settingsModel) + } + + composable( + typeMap = mapOf(typeOf() to AlbumType), + enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Up, + initialOffset = { it / 4 }) + scaleIn(initialScale = 3f / 4) + fadeIn() + }, + exitTransition = { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Down, + targetOffset = { it / 4 }) + scaleOut(targetScale = 3f / 4) + fadeOut() + } + ) { + val onlinePlaylistViewModel: OnlinePlaylistViewModel = + viewModel(factory = OnlinePlaylistViewModel.Factory) + AlbumScreen(onlinePlaylistViewModel.albumInfoState, playerViewModel) + } + + composable( + typeMap = mapOf(typeOf() to AlbumType), + enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Up, + initialOffset = { it / 4 }) + scaleIn(initialScale = 3f / 4) + fadeIn() + }, + exitTransition = { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Down, + targetOffset = { it / 4 }) + scaleOut(targetScale = 3f / 4) + fadeOut() + } + ) { + val localPlaylistViewModel: LocalPlaylistViewModel = + viewModel(factory = LocalPlaylistViewModel.Factory) + AlbumScreen(localPlaylistViewModel.albumInfoState, playerViewModel) + } + + composable( + typeMap = mapOf(typeOf() to AlbumType), + enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Up, + initialOffset = { it / 4 }) + scaleIn(initialScale = 3f / 4) + fadeIn() + }, + exitTransition = { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Down, + targetOffset = { it / 4 }) + scaleOut(targetScale = 3f / 4) + fadeOut() + } + ) { + val playlistInfoViewModel: PlaylistInfoViewModel = + viewModel(factory = PlaylistInfoViewModel.Factory) + AlbumScreen(playlistInfoViewModel.albumInfoState, playerViewModel) + } + + composable( + typeMap = mapOf(typeOf() to ArtistType), + enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Up, + initialOffset = { it / 4 }) + scaleIn(initialScale = 3f / 4) + fadeIn() + }, + exitTransition = { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Down, + targetOffset = { it / 4 }) + scaleOut(targetScale = 3f / 4) + fadeOut() + } + ) { + val onlineArtistViewModel: OnlineArtistViewModel = + viewModel(factory = OnlineArtistViewModel.Factory) + ArtistScreen(onClickAlbum = { + navHostController.navigate(Destination.Playlists(it)) + }, onlineArtistViewModel.artistInfoState) + } + + composable( + typeMap = mapOf(typeOf() to ArtistType), + enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Up, + initialOffset = { it / 4 }) + scaleIn(initialScale = 3f / 4) + fadeIn() + }, + exitTransition = { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Down, + targetOffset = { it / 4 }) + scaleOut(targetScale = 3f / 4) + fadeOut() + } + ) { + val localArtistViewModel: LocalArtistViewModel = + viewModel(factory = LocalArtistViewModel.Factory) + ArtistScreen(onClickAlbum = { + navHostController.navigate(Destination.LocalPlaylists(it)) + }, localArtistViewModel.artistInfoState) + } + } +} diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/ChipSelect.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/ChipSelect.kt similarity index 96% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/components/ChipSelect.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/components/ChipSelect.kt index 6825b28f..a0dc131b 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/ChipSelect.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/ChipSelect.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.ui.components +package app.suhasdissa.vibeyou.presentation.components import android.view.SoundEffectConstants import androidx.compose.foundation.layout.Arrangement diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/IconCard.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/IconCard.kt similarity index 95% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/components/IconCard.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/components/IconCard.kt index ea3a740f..3f5b2795 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/IconCard.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/IconCard.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.ui.components +package app.suhasdissa.vibeyou.presentation.components import androidx.annotation.StringRes import androidx.compose.foundation.layout.Box diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/IllustratedMessageScreen.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/IllustratedMessageScreen.kt similarity index 97% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/components/IllustratedMessageScreen.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/components/IllustratedMessageScreen.kt index e953e129..c4017bee 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/IllustratedMessageScreen.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/IllustratedMessageScreen.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.ui.components +package app.suhasdissa.vibeyou.presentation.components import androidx.annotation.DrawableRes import androidx.annotation.StringRes diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/LoadingScreen.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/LoadingScreen.kt similarity index 88% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/components/LoadingScreen.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/components/LoadingScreen.kt index c97992ab..fc9333d3 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/LoadingScreen.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/LoadingScreen.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.ui.components +package app.suhasdissa.vibeyou.presentation.components import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize diff --git a/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/MiniPlayerScaffold.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/MiniPlayerScaffold.kt new file mode 100644 index 00000000..1c92b6b8 --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/MiniPlayerScaffold.kt @@ -0,0 +1,127 @@ +package app.suhasdissa.vibeyou.presentation.components + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.BottomSheetDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Scaffold +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +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.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import app.suhasdissa.vibeyou.MellowMusicApplication +import app.suhasdissa.vibeyou.presentation.screens.player.FullScreenPlayer +import app.suhasdissa.vibeyou.presentation.screens.player.FullScreenPlayerHorizontal +import app.suhasdissa.vibeyou.presentation.screens.player.MiniPlayer +import app.suhasdissa.vibeyou.presentation.screens.player.components.EqualizerSheet +import app.suhasdissa.vibeyou.presentation.screens.player.components.QueueSheet +import app.suhasdissa.vibeyou.presentation.screens.player.components.SongOptionsSheet +import app.suhasdissa.vibeyou.presentation.screens.player.model.PlayerViewModel +import app.suhasdissa.vibeyou.utils.mediaItemState +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MiniPlayerScaffold( + playerViewModel: PlayerViewModel, + horizontalPlayer: Boolean = false, + content: @Composable (PaddingValues) -> Unit +) { + var isPlayerSheetVisible by remember { mutableStateOf(false) } + var showQueueSheet by remember { mutableStateOf(false) } + var showSongOptions by remember { mutableStateOf(false) } + var showEqualizerSheet by remember { + mutableStateOf(false) + } + val playerSheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = true + ) + val scope = rememberCoroutineScope() + if (isPlayerSheetVisible) { + ModalBottomSheet( + onDismissRequest = { isPlayerSheetVisible = false }, + sheetState = playerSheetState, + shape = RoundedCornerShape(8.dp), + dragHandle = null, + sheetMaxWidth = Dp.Unspecified, + windowInsets = if (horizontalPlayer) WindowInsets( + 0, + 0, + 0, + 0 + ) else BottomSheetDefaults.windowInsets + ) { + playerViewModel.controller?.let { controller -> + if (horizontalPlayer) { + Row(Modifier.fillMaxSize()) { + FullScreenPlayerHorizontal( + controller, + playerViewModel, + onClickShowQueueSheet = { showQueueSheet = true } + ) + } + } else { + FullScreenPlayer( + controller, + onCollapse = { + scope.launch { playerSheetState.hide() }.invokeOnCompletion { + if (!playerSheetState.isVisible) { + isPlayerSheetVisible = false + } + } + }, + playerViewModel, + onClickShowQueueSheet = { showQueueSheet = true }, + onClickShowSongOptions = { showSongOptions = true } + ) + } + } + } + } + if (showQueueSheet) QueueSheet(onDismissRequest = { showQueueSheet = false }, playerViewModel) + if (showSongOptions) SongOptionsSheet( + onDismissRequest = { showSongOptions = false }, + playerViewModel, + onClickShowEqualizer = { showEqualizerSheet = true } + ) + if (showEqualizerSheet) { + val app = LocalContext.current.applicationContext as MellowMusicApplication + EqualizerSheet( + equalizerData = app.supportedEqualizerData!!, + onDismissRequest = { showEqualizerSheet = false }, playerViewModel = playerViewModel + ) + } + Scaffold( + modifier = Modifier.fillMaxSize(), + bottomBar = { + playerViewModel.controller?.let { controller -> + val mediaItem by controller.mediaItemState() + mediaItem?.let { + MiniPlayer( + onClick = { + isPlayerSheetVisible = true + scope.launch { + playerSheetState.show() + } + }, + controller, + it, + playerViewModel + ) + } + } + }, content = content, contentWindowInsets = WindowInsets.systemBars + ) +} diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/NavDrawerContent.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/ModalNavDrawerContent.kt similarity index 51% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/components/NavDrawerContent.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/components/ModalNavDrawerContent.kt index 4a386a80..eecfa807 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/NavDrawerContent.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/ModalNavDrawerContent.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.ui.components +package app.suhasdissa.vibeyou.presentation.components import android.view.SoundEffectConstants import androidx.compose.foundation.layout.Column @@ -14,6 +14,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalDrawerSheet import androidx.compose.material3.NavigationDrawerItem +import androidx.compose.material3.PermanentDrawerSheet import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -22,13 +23,13 @@ import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import app.suhasdissa.vibeyou.Destination import app.suhasdissa.vibeyou.R +import app.suhasdissa.vibeyou.navigation.Destination @Composable -fun NavDrawerContent( +fun ModalNavDrawerContent( currentDestination: Destination, - onDestinationSelected: (Destination) -> Unit + onDestinationSelected: (Destination) -> Unit, ) { val view = LocalView.current ModalDrawerSheet(modifier = Modifier.width(250.dp)) { @@ -58,7 +59,7 @@ fun NavDrawerContent( ) }, label = { Text(text = stringResource(id = R.string.local_music)) }, - selected = currentDestination == Destination.LocalMusic, + selected = currentDestination is Destination.LocalMusic, onClick = { view.playSoundEffect(SoundEffectConstants.CLICK) onDestinationSelected(Destination.LocalMusic) @@ -73,10 +74,10 @@ fun NavDrawerContent( ) }, label = { Text(text = stringResource(id = R.string.piped_music)) }, - selected = currentDestination == Destination.PipedMusic, + selected = currentDestination is Destination.OnlineMusic, onClick = { view.playSoundEffect(SoundEffectConstants.CLICK) - onDestinationSelected(Destination.PipedMusic) + onDestinationSelected(Destination.OnlineMusic) } ) Spacer(Modifier.height(16.dp)) @@ -88,7 +89,79 @@ fun NavDrawerContent( ) }, label = { Text(text = stringResource(id = R.string.settings_title)) }, - selected = false, + selected = currentDestination is Destination.Settings, + onClick = { + view.playSoundEffect(SoundEffectConstants.CLICK) + onDestinationSelected(Destination.Settings) + } + ) + } +} + +@Composable +fun PermanentNavDrawerContent( + currentDestination: Destination, + onDestinationSelected: (Destination) -> Unit, +) { + val view = LocalView.current + PermanentDrawerSheet(modifier = Modifier.width(250.dp), drawerTonalElevation = 2.dp) { + Spacer(Modifier.height(16.dp)) + Column( + modifier = Modifier + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + modifier = Modifier.size(128.dp), + painter = painterResource(id = R.drawable.ic_launcher_foreground), + contentDescription = null + ) + Text( + stringResource(id = R.string.app_name), + color = MaterialTheme.colorScheme.primary, + style = MaterialTheme.typography.titleLarge + ) + } + Spacer(Modifier.height(16.dp)) + NavigationDrawerItem( + icon = { + Icon( + imageVector = Icons.Rounded.MusicNote, + contentDescription = null + ) + }, + label = { Text(text = stringResource(id = R.string.local_music)) }, + selected = currentDestination is Destination.LocalMusic, + onClick = { + view.playSoundEffect(SoundEffectConstants.CLICK) + onDestinationSelected(Destination.LocalMusic) + } + ) + Spacer(Modifier.height(16.dp)) + NavigationDrawerItem( + icon = { + Icon( + painter = painterResource(id = R.drawable.ic_piped), + contentDescription = null + ) + }, + label = { Text(text = stringResource(id = R.string.piped_music)) }, + selected = currentDestination is Destination.OnlineMusic, + onClick = { + view.playSoundEffect(SoundEffectConstants.CLICK) + onDestinationSelected(Destination.OnlineMusic) + } + ) + Spacer(Modifier.height(16.dp)) + NavigationDrawerItem( + icon = { + Icon( + imageVector = Icons.Rounded.Settings, + contentDescription = null + ) + }, + label = { Text(text = stringResource(id = R.string.settings_title)) }, + selected = currentDestination is Destination.Settings, onClick = { view.playSoundEffect(SoundEffectConstants.CLICK) onDestinationSelected(Destination.Settings) diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/SongCard.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/SongCard.kt similarity index 96% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/components/SongCard.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/components/SongCard.kt index 4c84b488..12d33186 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/SongCard.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/SongCard.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.ui.components +package app.suhasdissa.vibeyou.presentation.components import android.net.Uri import android.view.SoundEffectConstants @@ -36,7 +36,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import app.suhasdissa.vibeyou.R -import app.suhasdissa.vibeyou.backend.data.Song +import app.suhasdissa.vibeyou.domain.models.primary.Song import coil.compose.AsyncImage @OptIn(ExperimentalFoundationApi::class) @@ -134,7 +134,9 @@ fun SongCardCompact( ) { Icon( Icons.Rounded.BarChart, - modifier = Modifier.fillMaxSize().padding(8.dp), + modifier = Modifier + .fillMaxSize() + .padding(8.dp), contentDescription = null, tint = MaterialTheme.colorScheme.onSecondaryContainer ) diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/SongList.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/SongList.kt similarity index 91% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/components/SongList.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/components/SongList.kt index 47d32c22..76453385 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/SongList.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/SongList.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.ui.components +package app.suhasdissa.vibeyou.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues @@ -10,7 +10,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import app.suhasdissa.vibeyou.R -import app.suhasdissa.vibeyou.backend.data.Song +import app.suhasdissa.vibeyou.domain.models.primary.Song @Composable fun SongList( diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/songs/SongListView.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/SongListView.kt similarity index 51% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/screens/songs/SongListView.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/components/SongListView.kt index ce76dbab..ebedded0 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/songs/SongListView.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/SongListView.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.ui.screens.songs +package app.suhasdissa.vibeyou.presentation.components import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background @@ -23,23 +23,18 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel import app.suhasdissa.vibeyou.R -import app.suhasdissa.vibeyou.backend.data.Song -import app.suhasdissa.vibeyou.backend.viewmodel.PlayerViewModel -import app.suhasdissa.vibeyou.ui.components.IllustratedMessageScreen -import app.suhasdissa.vibeyou.ui.components.SongCard -import app.suhasdissa.vibeyou.ui.components.SongSettingsSheet -import app.suhasdissa.vibeyou.ui.dialogs.SortOrder +import app.suhasdissa.vibeyou.domain.models.primary.Song +import app.suhasdissa.vibeyou.presentation.screens.localmusic.components.SortOrder +import app.suhasdissa.vibeyou.presentation.screens.player.model.PlayerViewModel import app.suhasdissa.vibeyou.utils.TimeUtil -import my.nanihadesuka.compose.LazyColumnScrollbar @OptIn(ExperimentalFoundationApi::class) @Composable fun SongListView( songs: List, - sortOrder: SortOrder = SortOrder.Alphabetic, - playerViewModel: PlayerViewModel = viewModel(factory = PlayerViewModel.Factory) + playerViewModel: PlayerViewModel, + sortOrder: SortOrder = SortOrder.Alphabetic ) { var showSongSettings by remember { mutableStateOf(false) } var selectedSong by remember { mutableStateOf(null) } @@ -55,6 +50,7 @@ fun SongListView( song.creationDate ?: 0 ).toString() } + SortOrder.Date_Added -> songs.groupBy { song -> TimeUtil.getYear( song.dateAdded ?: 0 @@ -63,59 +59,60 @@ fun SongListView( } } val state = rememberLazyListState() - LazyColumnScrollbar( - listState = state, - thumbColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.5f), - thumbSelectedColor = MaterialTheme.colorScheme.primary, - thickness = 8.dp +// LazyColumnScrollbar( +// listState = state, +// thumbColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.5f), +// thumbSelectedColor = MaterialTheme.colorScheme.primary, +// thickness = 8.dp +// ) { + LazyColumn( + Modifier.fillMaxSize(), + state = state, + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp), + contentPadding = PaddingValues(horizontal = 8.dp, vertical = 8.dp) ) { - LazyColumn( - Modifier.fillMaxSize(), - state = state, - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(8.dp), - contentPadding = PaddingValues(horizontal = 8.dp, vertical = 8.dp) - ) { - groups.forEach { group -> - stickyHeader { - Row( - Modifier + groups.forEach { group -> + stickyHeader { + Row( + Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.background) + ) { + Text( + text = group.key, + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSecondaryContainer, + modifier = Modifier .fillMaxWidth() - .background(MaterialTheme.colorScheme.background) - ) { - Text( - text = group.key, - style = MaterialTheme.typography.titleMedium, - color = MaterialTheme.colorScheme.onSecondaryContainer, - modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(50)) - .background(MaterialTheme.colorScheme.secondaryContainer) - .padding(vertical = 8.dp, horizontal = 16.dp) - ) - } - } - items(items = group.value) { item -> - SongCard( - song = item, - onClickCard = { - playerViewModel.playSong(item) - }, - onLongPress = { - selectedSong = item - showSongSettings = true - } + .clip(RoundedCornerShape(50)) + .background(MaterialTheme.colorScheme.secondaryContainer) + .padding(vertical = 8.dp, horizontal = 16.dp) ) } } + items(items = group.value) { item -> + SongCard( + song = item, + onClickCard = { + playerViewModel.playSong(item) + }, + onLongPress = { + selectedSong = item + showSongSettings = true + } + ) + } } } + //} } if (showSongSettings) { selectedSong?.let { SongSettingsSheet( onDismissRequest = { showSongSettings = false }, - song = selectedSong!! + song = selectedSong!!, + playerViewModel ) } } diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/SongSettingsSheet.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/SongSettingsSheet.kt similarity index 93% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/components/SongSettingsSheet.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/components/SongSettingsSheet.kt index fe4d8882..d82b89c8 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/SongSettingsSheet.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/SongSettingsSheet.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.ui.components +package app.suhasdissa.vibeyou.presentation.components import android.view.SoundEffectConstants import androidx.annotation.StringRes @@ -13,11 +13,11 @@ import androidx.compose.foundation.layout.size 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.QueueMusic import androidx.compose.material.icons.rounded.Delete import androidx.compose.material.icons.rounded.Favorite import androidx.compose.material.icons.rounded.FavoriteBorder import androidx.compose.material.icons.rounded.PlayArrow -import androidx.compose.material.icons.rounded.QueueMusic import androidx.compose.material.icons.rounded.QueuePlayNext import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -43,9 +43,9 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import app.suhasdissa.vibeyou.R -import app.suhasdissa.vibeyou.backend.data.Song -import app.suhasdissa.vibeyou.backend.viewmodel.PlayerViewModel -import app.suhasdissa.vibeyou.backend.viewmodel.SongViewModel +import app.suhasdissa.vibeyou.domain.models.primary.Song +import app.suhasdissa.vibeyou.presentation.screens.onlinemusic.model.SongOptionsViewModel +import app.suhasdissa.vibeyou.presentation.screens.player.model.PlayerViewModel import coil.compose.AsyncImage @OptIn(ExperimentalMaterial3Api::class) @@ -53,8 +53,8 @@ import coil.compose.AsyncImage fun SongSettingsSheet( onDismissRequest: () -> Unit, song: Song, - songViewModel: SongViewModel = viewModel(factory = SongViewModel.Factory), - playerViewModel: PlayerViewModel = viewModel(factory = PlayerViewModel.Factory) + playerViewModel: PlayerViewModel, + songViewModel: SongOptionsViewModel = viewModel(factory = SongOptionsViewModel.Factory), ) { val songSettingsSheetState = rememberModalBottomSheetState( skipPartiallyExpanded = true @@ -140,7 +140,7 @@ fun SongSettingsSheet( } ) SheetSettingItem( - icon = Icons.Rounded.QueueMusic, + icon = Icons.AutoMirrored.Rounded.QueueMusic, description = R.string.enqueue_song, onClick = { playerViewModel.enqueueSong(song) @@ -166,7 +166,7 @@ fun SongSettingsSheet( fun SongSettingsSheetSearchPage( onDismissRequest: () -> Unit, song: Song, - playerViewModel: PlayerViewModel = viewModel(factory = PlayerViewModel.Factory) + playerViewModel: PlayerViewModel ) { val songSettingsSheetState = rememberModalBottomSheetState( skipPartiallyExpanded = true @@ -238,7 +238,7 @@ fun SongSettingsSheetSearchPage( } ) SheetSettingItem( - icon = Icons.Rounded.QueueMusic, + icon = Icons.AutoMirrored.Rounded.QueueMusic, description = R.string.enqueue_song, onClick = { playerViewModel.enqueueSong(song) diff --git a/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/VerticalSlider.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/VerticalSlider.kt new file mode 100644 index 00000000..8bef8f9d --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/VerticalSlider.kt @@ -0,0 +1,56 @@ +package app.suhasdissa.vibeyou.presentation.components + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.material3.Slider +import androidx.compose.material3.SliderColors +import androidx.compose.material3.SliderDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.TransformOrigin +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.layout.layout +import androidx.compose.ui.unit.Constraints + +@Composable +fun VerticalSlider( + value: Float, + onValueChange: (Float) -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + valueRange: ClosedFloatingPointRange = 0f..1f, + steps: Int = 0, + onValueChangeFinished: (() -> Unit)? = null, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + colors: SliderColors = SliderDefaults.colors() +) { + Slider( + colors = colors, + interactionSource = interactionSource, + onValueChangeFinished = onValueChangeFinished, + steps = steps, + valueRange = valueRange, + enabled = enabled, + value = value, + onValueChange = onValueChange, + modifier = Modifier + .graphicsLayer { + rotationZ = 270f + transformOrigin = TransformOrigin(0f, 0f) + } + .layout { measurable, constraints -> + val placeable = measurable.measure( + Constraints( + minWidth = constraints.minHeight, + maxWidth = constraints.maxHeight, + minHeight = constraints.minWidth, + maxHeight = constraints.maxHeight, + ) + ) + layout(placeable.height, placeable.width) { + placeable.place(-placeable.width, 0) + } + } + .then(modifier) + ) +} \ No newline at end of file diff --git a/app/src/main/java/app/suhasdissa/vibeyou/presentation/layout/MainAppContent.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/layout/MainAppContent.kt new file mode 100644 index 00000000..ae5e660d --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/layout/MainAppContent.kt @@ -0,0 +1,53 @@ +package app.suhasdissa.vibeyou.presentation.layout + +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo +import androidx.compose.runtime.Composable +import androidx.navigation.compose.rememberNavController +import androidx.window.core.layout.WindowHeightSizeClass +import androidx.window.core.layout.WindowSizeClass +import androidx.window.core.layout.WindowWidthSizeClass +import app.suhasdissa.vibeyou.presentation.screens.player.model.PlayerViewModel +import app.suhasdissa.vibeyou.presentation.screens.settings.model.SettingsModel + +@Composable +fun MainAppContent( + playerViewModel: PlayerViewModel, + settingsModel: SettingsModel, + windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass +) { + val navHostController = rememberNavController() + if (windowSizeClass.windowHeightSizeClass != WindowHeightSizeClass.COMPACT) { + when (windowSizeClass.windowWidthSizeClass) { + WindowWidthSizeClass.COMPACT -> { + ModelNavDrawerLayout( + navHostController, + playerViewModel, + settingsModel + ) + } + + WindowWidthSizeClass.MEDIUM -> { + PermanentNavDrawerLayout( + navHostController, + playerViewModel, + settingsModel + ) + } + + WindowWidthSizeClass.EXPANDED -> { + PermanentNavDrawerWithPlayerLayout( + navHostController, + playerViewModel, + settingsModel + ) + } + } + } else { + PermanentNavDrawerLayout( + navHostController, + playerViewModel, + settingsModel, + horizontalPlayer = true + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/app/suhasdissa/vibeyou/presentation/layout/ModelNavDrawerLayout.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/layout/ModelNavDrawerLayout.kt new file mode 100644 index 00000000..7cadd386 --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/layout/ModelNavDrawerLayout.kt @@ -0,0 +1,90 @@ +package app.suhasdissa.vibeyou.presentation.layout + +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.DrawerValue +import androidx.compose.material3.ModalNavigationDrawer +import androidx.compose.material3.rememberDrawerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +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.Modifier +import androidx.navigation.NavController +import androidx.navigation.NavDestination.Companion.hasRoute +import androidx.navigation.NavHostController +import app.suhasdissa.vibeyou.navigation.AppNavHost +import app.suhasdissa.vibeyou.navigation.Destination +import app.suhasdissa.vibeyou.presentation.components.MiniPlayerScaffold +import app.suhasdissa.vibeyou.presentation.components.ModalNavDrawerContent +import app.suhasdissa.vibeyou.presentation.screens.player.model.PlayerViewModel +import app.suhasdissa.vibeyou.presentation.screens.settings.model.SettingsModel +import kotlinx.coroutines.launch + +@Composable +internal fun ModelNavDrawerLayout( + navHostController: NavHostController, + playerViewModel: PlayerViewModel, + settingsModel: SettingsModel +) { + val drawerState = rememberDrawerState(DrawerValue.Closed) + val scope = rememberCoroutineScope() + var currentDestination by remember { + mutableStateOf(Destination.LocalMusic) + } + + DisposableEffect(Unit) { + val listener = NavController.OnDestinationChangedListener { _, destination, _ -> + listOf( + Destination.LocalMusic, + Destination.OnlineMusic, + Destination.Settings + ).firstOrNull { destination.hasRoute(it::class) } + ?.let { currentDestination = it } + } + navHostController.addOnDestinationChangedListener(listener) + + onDispose { + navHostController.removeOnDestinationChangedListener(listener) + } + } + + ModalNavigationDrawer( + drawerState = drawerState, + gesturesEnabled = drawerState.isOpen, + drawerContent = { + ModalNavDrawerContent( + currentDestination = currentDestination, + onDestinationSelected = { + scope.launch { + drawerState.close() + } + navHostController.popBackStack() + navHostController.navigate(it) + } + ) + } + ) { + MiniPlayerScaffold(playerViewModel) { pV -> + AppNavHost( + modifier = Modifier + .fillMaxSize() + .consumeWindowInsets(pV) + .padding(pV), + navHostController = navHostController, + onDrawerOpen = { + scope.launch { + drawerState.open() + } + }, + playerViewModel, + settingsModel + ) + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/app/suhasdissa/vibeyou/presentation/layout/PermanentNavDrawerLayout.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/layout/PermanentNavDrawerLayout.kt new file mode 100644 index 00000000..f321a650 --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/layout/PermanentNavDrawerLayout.kt @@ -0,0 +1,74 @@ +package app.suhasdissa.vibeyou.presentation.layout + +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.PermanentNavigationDrawer +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.navigation.NavController +import androidx.navigation.NavDestination.Companion.hasRoute +import androidx.navigation.NavHostController +import app.suhasdissa.vibeyou.navigation.AppNavHost +import app.suhasdissa.vibeyou.navigation.Destination +import app.suhasdissa.vibeyou.presentation.components.MiniPlayerScaffold +import app.suhasdissa.vibeyou.presentation.components.PermanentNavDrawerContent +import app.suhasdissa.vibeyou.presentation.screens.player.model.PlayerViewModel +import app.suhasdissa.vibeyou.presentation.screens.settings.model.SettingsModel + +@Composable +internal fun PermanentNavDrawerLayout( + navHostController: NavHostController, + playerViewModel: PlayerViewModel, + settingsModel: SettingsModel, + horizontalPlayer: Boolean = false +) { + var currentDestination by remember { + mutableStateOf(Destination.LocalMusic) + } + + DisposableEffect(Unit) { + val listener = NavController.OnDestinationChangedListener { _, destination, _ -> + listOf( + Destination.LocalMusic, + Destination.OnlineMusic, + Destination.Settings + ).firstOrNull { destination.hasRoute(it::class) } + ?.let { currentDestination = it } + } + navHostController.addOnDestinationChangedListener(listener) + + onDispose { + navHostController.removeOnDestinationChangedListener(listener) + } + } + + PermanentNavigationDrawer(drawerContent = { + PermanentNavDrawerContent( + currentDestination = currentDestination, + onDestinationSelected = { + currentDestination = it + navHostController.popBackStack() + navHostController.navigate(it) + } + ) + }) { + MiniPlayerScaffold(playerViewModel, horizontalPlayer = horizontalPlayer) { pV -> + AppNavHost( + modifier = Modifier + .fillMaxSize() + .consumeWindowInsets(pV) + .padding(pV), + navHostController = navHostController, + onDrawerOpen = null, + playerViewModel, + settingsModel + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/suhasdissa/vibeyou/presentation/layout/PermanentNavDrawerWithPlayerLayout.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/layout/PermanentNavDrawerWithPlayerLayout.kt new file mode 100644 index 00000000..fd5213fc --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/layout/PermanentNavDrawerWithPlayerLayout.kt @@ -0,0 +1,122 @@ +package app.suhasdissa.vibeyou.presentation.layout + +import androidx.compose.animation.AnimatedVisibility +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.width +import androidx.compose.material3.PermanentNavigationDrawer +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import androidx.navigation.NavDestination.Companion.hasRoute +import androidx.navigation.NavHostController +import app.suhasdissa.vibeyou.MellowMusicApplication +import app.suhasdissa.vibeyou.navigation.AppNavHost +import app.suhasdissa.vibeyou.navigation.Destination +import app.suhasdissa.vibeyou.presentation.components.PermanentNavDrawerContent +import app.suhasdissa.vibeyou.presentation.screens.player.FullScreenPlayer +import app.suhasdissa.vibeyou.presentation.screens.player.components.EqualizerSheet +import app.suhasdissa.vibeyou.presentation.screens.player.components.QueueSheet +import app.suhasdissa.vibeyou.presentation.screens.player.components.SongOptionsSheet +import app.suhasdissa.vibeyou.presentation.screens.player.model.PlayerViewModel +import app.suhasdissa.vibeyou.presentation.screens.settings.model.SettingsModel +import app.suhasdissa.vibeyou.utils.mediaItemState + +@Composable +internal fun PermanentNavDrawerWithPlayerLayout( + navHostController: NavHostController, + playerViewModel: PlayerViewModel, + settingsModel: SettingsModel +) { + var currentDestination by remember { + mutableStateOf(Destination.LocalMusic) + } + + DisposableEffect(Unit) { + val listener = NavController.OnDestinationChangedListener { _, destination, _ -> + listOf( + Destination.LocalMusic, + Destination.OnlineMusic, + Destination.Settings + ).firstOrNull { destination.hasRoute(it::class) } + ?.let { currentDestination = it } + } + navHostController.addOnDestinationChangedListener(listener) + + onDispose { + navHostController.removeOnDestinationChangedListener(listener) + } + } + + PermanentNavigationDrawer(drawerContent = { + PermanentNavDrawerContent( + currentDestination = currentDestination, + onDestinationSelected = { + navHostController.popBackStack() + navHostController.navigate(it) + } + ) + }) { + Row(Modifier.fillMaxSize()) { + AppNavHost( + modifier = Modifier + .fillMaxHeight() + .weight(1f), + navHostController = navHostController, + onDrawerOpen = null, + playerViewModel, + settingsModel + ) + + playerViewModel.controller?.let { controller -> + val mediaItem by controller.mediaItemState() + AnimatedVisibility(visible = mediaItem != null) { + Column( + Modifier + .fillMaxHeight() + .width(400.dp) + ) { + var showQueueSheet by remember { mutableStateOf(false) } + var showSongOptions by remember { mutableStateOf(false) } + var showEqualizerSheet by remember { + mutableStateOf(false) + } + FullScreenPlayer( + controller, + onCollapse = null, + playerViewModel, + onClickShowQueueSheet = { showQueueSheet = true }, + onClickShowSongOptions = { showSongOptions = true } + ) + if (showQueueSheet) QueueSheet(onDismissRequest = { + showQueueSheet = false + }, playerViewModel) + if (showSongOptions) SongOptionsSheet( + onDismissRequest = { showSongOptions = false }, + playerViewModel, + onClickShowEqualizer = { showEqualizerSheet = true } + ) + if (showEqualizerSheet) { + val app = + LocalContext.current.applicationContext as MellowMusicApplication + EqualizerSheet( + equalizerData = app.supportedEqualizerData!!, + onDismissRequest = { showEqualizerSheet = false }, + playerViewModel = playerViewModel + ) + } + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/album/AlbumScreen.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/album/AlbumScreen.kt new file mode 100644 index 00000000..47561f6f --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/album/AlbumScreen.kt @@ -0,0 +1,259 @@ +package app.suhasdissa.vibeyou.presentation.screens.album + +import android.widget.Toast +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +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.LibraryAdd +import androidx.compose.material.icons.rounded.PlayArrow +import androidx.compose.material.icons.rounded.Shuffle +import androidx.compose.material3.Button +import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +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.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import app.suhasdissa.vibeyou.R +import app.suhasdissa.vibeyou.domain.models.primary.Song +import app.suhasdissa.vibeyou.presentation.components.IllustratedMessageScreen +import app.suhasdissa.vibeyou.presentation.components.LoadingScreen +import app.suhasdissa.vibeyou.presentation.components.SongCard +import app.suhasdissa.vibeyou.presentation.components.SongSettingsSheetSearchPage +import app.suhasdissa.vibeyou.presentation.screens.album.model.NewPlaylistViewModel +import app.suhasdissa.vibeyou.presentation.screens.onlinesearch.model.state.AlbumInfoState +import app.suhasdissa.vibeyou.presentation.screens.player.model.PlayerViewModel +import coil.compose.AsyncImage + +@Composable +fun AlbumScreen( + state: AlbumInfoState, + playerViewModel: PlayerViewModel, + playlistViewModel: NewPlaylistViewModel = viewModel(factory = NewPlaylistViewModel.Factory) +) { + when (state) { + AlbumInfoState.Error -> IllustratedMessageScreen( + image = R.drawable.ic_launcher_monochrome, + message = R.string.something_went_wrong + ) + + AlbumInfoState.Loading -> LoadingScreen() + is AlbumInfoState.Success -> { + var showSongSettings by remember { mutableStateOf(false) } + var selectedSong by remember { mutableStateOf(null) } + LazyColumn( + Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp), + contentPadding = PaddingValues(horizontal = 8.dp, vertical = 8.dp) + ) { + item { + AlbumHeader(state, playerViewModel, playlistViewModel) + } + items(items = state.songs) { item -> + SongCard( + song = item, + onClickCard = { + playerViewModel.playSong(item) + if (!item.isLocal) { + playerViewModel.saveSong(item) + } + }, + onLongPress = { + selectedSong = item + showSongSettings = true + } + ) + } + } + + if (showSongSettings) { + selectedSong?.let { + SongSettingsSheetSearchPage( + onDismissRequest = { showSongSettings = false }, + song = it, + playerViewModel + ) + } + } + } + } +} + +@Composable +private fun AlbumHeader( + state: AlbumInfoState.Success, + playerViewModel: PlayerViewModel, + playlistViewModel: NewPlaylistViewModel +) { + BoxWithConstraints(Modifier.fillMaxWidth()) { + if (maxWidth > 600.dp) { + Column { + Row( + Modifier + .fillMaxWidth() + .height(IntrinsicSize.Min), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + AsyncImage( + modifier = Modifier + .weight(1f) + .padding(horizontal = 40.dp, vertical = 20.dp) + .aspectRatio(1f) + .clip(RoundedCornerShape(16.dp)), + model = state.album.thumbnailUri, + contentDescription = stringResource(id = R.string.album_art), + contentScale = ContentScale.Crop, + error = painterResource(id = R.drawable.music_placeholder) + ) + Column( + Modifier + .weight(2f) + .fillMaxHeight() + .padding(vertical = 20.dp) + ) { + AlbumDetails( + state = state, + playerViewModel = playerViewModel, + playlistViewModel = playlistViewModel + ) + } + } + HorizontalDivider() + } + } else { + Column( + Modifier + .fillMaxWidth() + .padding(8.dp) + .padding(bottom = 8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + AsyncImage( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 40.dp, vertical = 20.dp) + .aspectRatio(1f) + .clip(RoundedCornerShape(16.dp)), + model = state.album.thumbnailUri, + contentDescription = stringResource(id = R.string.album_art), + contentScale = ContentScale.Crop, + error = painterResource(id = R.drawable.music_placeholder) + ) + AlbumDetails( + state = state, + playerViewModel = playerViewModel, + playlistViewModel = playlistViewModel + ) + HorizontalDivider() + } + } + } +} + +@Composable +private fun ColumnScope.AlbumDetails( + state: AlbumInfoState.Success, + playerViewModel: PlayerViewModel, + playlistViewModel: NewPlaylistViewModel +) { + Column( + Modifier + .fillMaxWidth() + .padding(8.dp) + ) { + Text( + text = state.album.title, + style = MaterialTheme.typography.titleLarge + ) + Text( + text = state.album.artistsText, + style = MaterialTheme.typography.bodyMedium + ) + state.album.numberOfSongs?.let { + Text( + text = "$it ${stringResource(id = R.string.songs)}", + style = MaterialTheme.typography.bodyMedium + ) + } + } + Spacer(modifier = Modifier.weight(1f)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + OutlinedButton(modifier = Modifier.weight(1f), onClick = { + playerViewModel.playSongs(state.songs, shuffle = false) + }) { + Icon( + imageVector = Icons.Rounded.PlayArrow, + contentDescription = null + ) + Text(text = stringResource(id = R.string.play_all)) + } + Button(modifier = Modifier.weight(1f), onClick = { + playerViewModel.playSongs(state.songs, shuffle = true) + }) { + Icon( + imageVector = Icons.Rounded.Shuffle, + contentDescription = null + ) + Text(text = stringResource(id = R.string.shuffle)) + } + } + if (!state.album.isLocal) { + val context = LocalContext.current + FilledTonalButton( + modifier = Modifier.fillMaxWidth(), + onClick = { + playlistViewModel.newPlaylistWithSongs( + state.album, + state.songs + ) + Toast.makeText( + context, + context.getString( + R.string.added_all_the_songs_to_the_library + ), + Toast.LENGTH_SHORT + ).show() + } + ) { + Icon( + imageVector = Icons.Rounded.LibraryAdd, + contentDescription = null + ) + Text(text = stringResource(R.string.add_to_library)) + } + } +} diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/AlbumCard.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/album/components/AlbumCard.kt similarity index 95% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/components/AlbumCard.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/album/components/AlbumCard.kt index 6f08fa4c..fa112060 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/AlbumCard.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/album/components/AlbumCard.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.ui.components +package app.suhasdissa.vibeyou.presentation.screens.album.components import android.view.SoundEffectConstants import androidx.compose.foundation.ExperimentalFoundationApi @@ -26,7 +26,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import app.suhasdissa.vibeyou.R -import app.suhasdissa.vibeyou.backend.data.Album +import app.suhasdissa.vibeyou.domain.models.primary.Album import coil.compose.AsyncImage @OptIn(ExperimentalFoundationApi::class) diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/AlbumList.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/album/components/AlbumList.kt similarity index 86% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/components/AlbumList.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/album/components/AlbumList.kt index 8e35db7b..15f8ac9a 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/AlbumList.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/album/components/AlbumList.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.ui.components +package app.suhasdissa.vibeyou.presentation.screens.album.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues @@ -10,7 +10,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import app.suhasdissa.vibeyou.R -import app.suhasdissa.vibeyou.backend.data.Album +import app.suhasdissa.vibeyou.domain.models.primary.Album +import app.suhasdissa.vibeyou.presentation.components.IllustratedMessageScreen @Composable fun AlbumList( diff --git a/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/album/model/LocalPlaylistViewModel.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/album/model/LocalPlaylistViewModel.kt new file mode 100644 index 00000000..db7e0eb1 --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/album/model/LocalPlaylistViewModel.kt @@ -0,0 +1,63 @@ +package app.suhasdissa.vibeyou.presentation.screens.album.model + +import android.util.Log +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.createSavedStateHandle +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory +import androidx.navigation.toRoute +import app.suhasdissa.vibeyou.MellowMusicApplication +import app.suhasdissa.vibeyou.backend.repository.LocalMusicRepository +import app.suhasdissa.vibeyou.domain.models.primary.Album +import app.suhasdissa.vibeyou.navigation.Destination +import app.suhasdissa.vibeyou.presentation.screens.onlinesearch.model.state.AlbumInfoState +import kotlinx.coroutines.launch + +class LocalPlaylistViewModel( + private val musicRepository: LocalMusicRepository, + savedStateHandle: SavedStateHandle +) : ViewModel() { + + val playlist = savedStateHandle.toRoute() + + var albumInfoState: AlbumInfoState by mutableStateOf(AlbumInfoState.Loading) + private set + + init { + getAlbumInfo(playlist.album) + } + + private fun getAlbumInfo(album: Album) { + viewModelScope.launch { + albumInfoState = AlbumInfoState.Loading + albumInfoState = try { + AlbumInfoState.Success( + album, + musicRepository.getAlbumInfo(album.id.toLong()) + ) + } catch (e: Exception) { + Log.e("Playlist Info", e.toString()) + AlbumInfoState.Error + } + } + } + + companion object { + val Factory: ViewModelProvider.Factory = viewModelFactory { + initializer { + val application = + (this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as MellowMusicApplication) + LocalPlaylistViewModel( + application.container.localMusicRepository, + this.createSavedStateHandle() + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/album/model/NewPlaylistViewModel.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/album/model/NewPlaylistViewModel.kt new file mode 100644 index 00000000..926a9113 --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/album/model/NewPlaylistViewModel.kt @@ -0,0 +1,32 @@ +package app.suhasdissa.vibeyou.presentation.screens.album.model + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory +import app.suhasdissa.vibeyou.MellowMusicApplication +import app.suhasdissa.vibeyou.backend.repository.PlaylistRepository +import app.suhasdissa.vibeyou.domain.models.primary.Album +import app.suhasdissa.vibeyou.domain.models.primary.Song +import kotlinx.coroutines.launch + +class NewPlaylistViewModel(private val playlistRepository: PlaylistRepository) : ViewModel() { + fun newPlaylistWithSongs(album: Album, songs: List) { + viewModelScope.launch { + playlistRepository.newPlaylistWithSongs(album, songs) + } + } + + companion object { + val Factory: ViewModelProvider.Factory = viewModelFactory { + initializer { + val application = + (this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as MellowMusicApplication) + NewPlaylistViewModel( + application.container.playlistRepository + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/album/model/OnlinePlaylistViewModel.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/album/model/OnlinePlaylistViewModel.kt new file mode 100644 index 00000000..bec877a5 --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/album/model/OnlinePlaylistViewModel.kt @@ -0,0 +1,65 @@ +package app.suhasdissa.vibeyou.presentation.screens.album.model + +import android.util.Log +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.createSavedStateHandle +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory +import androidx.navigation.toRoute +import app.suhasdissa.vibeyou.MellowMusicApplication +import app.suhasdissa.vibeyou.backend.repository.PipedMusicRepository +import app.suhasdissa.vibeyou.domain.models.primary.Album +import app.suhasdissa.vibeyou.navigation.Destination +import app.suhasdissa.vibeyou.presentation.screens.onlinesearch.model.state.AlbumInfoState +import app.suhasdissa.vibeyou.utils.asSong +import kotlinx.coroutines.launch + +class OnlinePlaylistViewModel( + private val musicRepository: PipedMusicRepository, + savedStateHandle: SavedStateHandle +) : ViewModel() { + + val playlist = savedStateHandle.toRoute() + + var albumInfoState: AlbumInfoState by mutableStateOf(AlbumInfoState.Loading) + private set + + init { + getPlaylistInfo(playlist.album) + } + + private fun getPlaylistInfo(playlist: Album) { + viewModelScope.launch { + albumInfoState = AlbumInfoState.Loading + albumInfoState = try { + val info = musicRepository.getPlaylistInfo(playlist.id) + AlbumInfoState.Success( + playlist, + info.relatedStreams.map { it.asSong } + ) + } catch (e: Exception) { + Log.e("Playlist Info", e.toString()) + AlbumInfoState.Error + } + } + } + + companion object { + val Factory: ViewModelProvider.Factory = viewModelFactory { + initializer { + val application = + (this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as MellowMusicApplication) + OnlinePlaylistViewModel( + application.container.pipedMusicRepository, + this.createSavedStateHandle() + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/artist/ArtistScreen.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/artist/ArtistScreen.kt new file mode 100644 index 00000000..e0690fda --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/artist/ArtistScreen.kt @@ -0,0 +1,57 @@ +package app.suhasdissa.vibeyou.presentation.screens.artist + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import app.suhasdissa.vibeyou.R +import app.suhasdissa.vibeyou.domain.models.primary.Album +import app.suhasdissa.vibeyou.presentation.components.IllustratedMessageScreen +import app.suhasdissa.vibeyou.presentation.components.LoadingScreen +import app.suhasdissa.vibeyou.presentation.screens.album.components.AlbumList +import app.suhasdissa.vibeyou.presentation.screens.onlinesearch.model.state.ArtistInfoState + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ArtistScreen( + onClickAlbum: (Album) -> Unit, + state: ArtistInfoState +) { + Scaffold(topBar = { + TopAppBar(title = { + when (state) { + is ArtistInfoState.Success -> Text( + text = state.artist.artistsText, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + + else -> Text(stringResource(R.string.artist)) + } + }, scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()) + }) { pV -> + Column(Modifier.padding(pV)) { + when (state) { + ArtistInfoState.Error -> IllustratedMessageScreen( + image = R.drawable.ic_launcher_monochrome, + message = R.string.something_went_wrong + ) + + ArtistInfoState.Loading -> LoadingScreen() + is ArtistInfoState.Success -> { + AlbumList(items = state.playlists, onClickCard = { + onClickAlbum.invoke(it) + }, onLongPress = { + }) + } + } + } + } +} diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/ArtistCard.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/artist/components/ArtistCard.kt similarity index 96% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/components/ArtistCard.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/artist/components/ArtistCard.kt index 517dc623..917e9568 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/ArtistCard.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/artist/components/ArtistCard.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.ui.components +package app.suhasdissa.vibeyou.presentation.screens.artist.components import android.view.SoundEffectConstants import androidx.compose.foundation.ExperimentalFoundationApi @@ -26,7 +26,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import app.suhasdissa.vibeyou.R -import app.suhasdissa.vibeyou.backend.data.Artist +import app.suhasdissa.vibeyou.domain.models.primary.Artist import coil.compose.AsyncImage @OptIn(ExperimentalFoundationApi::class) diff --git a/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/artist/components/ArtistList.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/artist/components/ArtistList.kt new file mode 100644 index 00000000..0b306530 --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/artist/components/ArtistList.kt @@ -0,0 +1,47 @@ +package app.suhasdissa.vibeyou.presentation.screens.artist.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import app.suhasdissa.vibeyou.R +import app.suhasdissa.vibeyou.domain.models.primary.Artist +import app.suhasdissa.vibeyou.presentation.components.IllustratedMessageScreen + +@Composable +fun ArtistList( + items: List, + onClickCard: (artist: Artist) -> Unit, + onLongPress: (artist: Artist) -> Unit +) { + if (items.isEmpty()) { + IllustratedMessageScreen(image = R.drawable.ic_launcher_monochrome) + } + val state = rememberLazyListState() +// LazyColumnScrollbar( +// listState = state, +// thumbColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.5f), +// thumbSelectedColor = MaterialTheme.colorScheme.primary, +// thickness = 8.dp +// ) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + state = state, + verticalArrangement = Arrangement.spacedBy(8.dp), + contentPadding = PaddingValues(horizontal = 8.dp, vertical = 8.dp) + ) { + items(items = items) { item -> + ArtistCard( + artist = item, + onClickCard = { onClickCard(item) }, + onLongPress = { onLongPress(item) } + ) + } + } +// } +} diff --git a/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/artist/model/LocalArtistViewModel.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/artist/model/LocalArtistViewModel.kt new file mode 100644 index 00000000..75ebee68 --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/artist/model/LocalArtistViewModel.kt @@ -0,0 +1,62 @@ +package app.suhasdissa.vibeyou.presentation.screens.artist.model + +import android.util.Log +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.createSavedStateHandle +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory +import androidx.navigation.toRoute +import app.suhasdissa.vibeyou.MellowMusicApplication +import app.suhasdissa.vibeyou.backend.repository.LocalMusicRepository +import app.suhasdissa.vibeyou.domain.models.primary.Artist +import app.suhasdissa.vibeyou.navigation.Destination +import app.suhasdissa.vibeyou.presentation.screens.onlinesearch.model.state.ArtistInfoState +import kotlinx.coroutines.launch + +class LocalArtistViewModel( + private val musicRepository: LocalMusicRepository, + savedStateHandle: SavedStateHandle +) : ViewModel() { + + val artist = savedStateHandle.toRoute() + var artistInfoState: ArtistInfoState by mutableStateOf(ArtistInfoState.Loading) + private set + + init { + getArtistInfo(artist.artist) + } + + private fun getArtistInfo(artist: Artist) { + viewModelScope.launch { + artistInfoState = ArtistInfoState.Loading + artistInfoState = try { + ArtistInfoState.Success( + artist, + musicRepository.getArtistInfo(artist.artistsText) + ) + } catch (e: Exception) { + Log.e("Artist Info", e.toString()) + ArtistInfoState.Error + } + } + } + + companion object { + val Factory: ViewModelProvider.Factory = viewModelFactory { + initializer { + val application = + (this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as MellowMusicApplication) + LocalArtistViewModel( + application.container.localMusicRepository, + this.createSavedStateHandle() + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/artist/model/OnlineArtistViewModel.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/artist/model/OnlineArtistViewModel.kt new file mode 100644 index 00000000..185f7b70 --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/artist/model/OnlineArtistViewModel.kt @@ -0,0 +1,67 @@ +package app.suhasdissa.vibeyou.presentation.screens.artist.model + +import android.util.Log +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.core.net.toUri +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.createSavedStateHandle +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory +import androidx.navigation.toRoute +import app.suhasdissa.vibeyou.MellowMusicApplication +import app.suhasdissa.vibeyou.backend.repository.PipedMusicRepository +import app.suhasdissa.vibeyou.domain.models.primary.Artist +import app.suhasdissa.vibeyou.navigation.Destination +import app.suhasdissa.vibeyou.presentation.screens.onlinesearch.model.state.ArtistInfoState +import kotlinx.coroutines.launch + +class OnlineArtistViewModel( + private val musicRepository: PipedMusicRepository, + savedStateHandle: SavedStateHandle +) : ViewModel() { + val artist = savedStateHandle.toRoute() + var artistInfoState: ArtistInfoState by mutableStateOf(ArtistInfoState.Loading) + private set + + init { + getArtistInfo(artist.artist) + } + + private fun getArtistInfo(artist: Artist) { + viewModelScope.launch { + artistInfoState = ArtistInfoState.Loading + artistInfoState = try { + val info = musicRepository.getChannelInfo(artist.id) + val playlists = musicRepository.getChannelPlaylists(artist.id, info.tabs) + ArtistInfoState.Success( + artist.copy( + thumbnailUri = info.avatarUrl?.toUri(), + description = info.description + ), + playlists + ) + } catch (e: Exception) { + Log.e("Playlist Info", e.toString()) + ArtistInfoState.Error + } + } + } + + companion object { + val Factory: ViewModelProvider.Factory = viewModelFactory { + initializer { + val application = + (this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as MellowMusicApplication) + OnlineArtistViewModel( + application.container.pipedMusicRepository, + this.createSavedStateHandle() + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/music/LocalMusicScreen.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/localmusic/LocalMusicScreen.kt similarity index 57% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/screens/music/LocalMusicScreen.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/localmusic/LocalMusicScreen.kt index 2f77b18d..d62a2ee6 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/music/LocalMusicScreen.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/localmusic/LocalMusicScreen.kt @@ -1,30 +1,36 @@ -package app.suhasdissa.vibeyou.ui.screens.music +package app.suhasdissa.vibeyou.presentation.screens.localmusic import android.view.SoundEffectConstants -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.rounded.Sort +import androidx.compose.material.icons.rounded.Menu import androidx.compose.material.icons.rounded.PlayArrow +import androidx.compose.material.icons.rounded.Search import androidx.compose.material.icons.rounded.Shuffle -import androidx.compose.material.icons.rounded.Sort +import androidx.compose.material3.Card 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.material3.Tab import androidx.compose.material3.TabRow import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -34,29 +40,111 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.core.content.edit import androidx.lifecycle.viewmodel.compose.viewModel -import app.suhasdissa.vibeyou.Destination +import app.suhasdissa.vibeyou.MainActivity import app.suhasdissa.vibeyou.R -import app.suhasdissa.vibeyou.backend.viewmodel.LocalSearchViewModel -import app.suhasdissa.vibeyou.backend.viewmodel.LocalSongViewModel -import app.suhasdissa.vibeyou.backend.viewmodel.PlayerViewModel -import app.suhasdissa.vibeyou.ui.components.AlbumList -import app.suhasdissa.vibeyou.ui.components.ArtistList -import app.suhasdissa.vibeyou.ui.dialogs.SortOrderDialog -import app.suhasdissa.vibeyou.ui.screens.songs.SongListView +import app.suhasdissa.vibeyou.backend.repository.LocalMusicRepository +import app.suhasdissa.vibeyou.navigation.Destination +import app.suhasdissa.vibeyou.presentation.components.SongListView +import app.suhasdissa.vibeyou.presentation.screens.album.components.AlbumList +import app.suhasdissa.vibeyou.presentation.screens.artist.components.ArtistList +import app.suhasdissa.vibeyou.presentation.screens.localmusic.components.SortOrderDialog +import app.suhasdissa.vibeyou.presentation.screens.localmusic.model.LocalSongViewModel +import app.suhasdissa.vibeyou.presentation.screens.player.model.PlayerViewModel +import app.suhasdissa.vibeyou.utils.PermissionHelper import app.suhasdissa.vibeyou.utils.Pref import kotlinx.coroutines.launch -@OptIn(ExperimentalFoundationApi::class) @Composable fun LocalMusicScreen( onNavigate: (Destination) -> Unit, - localSearchViewModel: LocalSearchViewModel, + playerViewModel: PlayerViewModel, + onDrawerOpen: (() -> Unit)?, localSongViewModel: LocalSongViewModel = viewModel(factory = LocalSongViewModel.Factory), - playerViewModel: PlayerViewModel = viewModel(factory = PlayerViewModel.Factory) +) { + val mainActivity = LocalView.current.context as MainActivity + LaunchedEffect(Unit) { + PermissionHelper.checkPermissions( + mainActivity, + LocalMusicRepository.permissions, + ) + } + val view = LocalView.current + Scaffold(topBar = { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + if (onDrawerOpen != null) { + IconButton(onClick = { + view.playSoundEffect(SoundEffectConstants.CLICK) + onDrawerOpen() + }) { + Icon( + imageVector = Icons.Rounded.Menu, + contentDescription = "Open Navigation Drawer", + ) + } + } else { + Spacer(modifier = Modifier.width(8.dp)) + } + Card( + modifier = + Modifier + .fillMaxWidth() + .padding(vertical = 12.dp) + .padding(end = 8.dp) + .clickable { + view.playSoundEffect(SoundEffectConstants.CLICK) + onNavigate(Destination.LocalSearch) + }, + shape = RoundedCornerShape(40), + ) { + Row( + modifier = + Modifier + .fillMaxWidth() + .padding(vertical = 2.dp) + .padding(end = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + modifier = Modifier.size(48.dp), + painter = painterResource(id = R.drawable.ic_launcher_foreground), + contentDescription = null, + ) + Text(stringResource(R.string.search_songs)) + Spacer(Modifier.weight(1f)) + Icon( + modifier = Modifier.padding(8.dp), + imageVector = Icons.Rounded.Search, + contentDescription = null, + ) + } + } + } + }) { pV -> + LocalMusicScreenContent( + modifier = + Modifier + .consumeWindowInsets(pV) + .padding(pV), + onNavigate, + playerViewModel, + localSongViewModel, + ) + } +} + +@Composable +fun LocalMusicScreenContent( + modifier: Modifier = Modifier, + onNavigate: (Destination) -> Unit, + playerViewModel: PlayerViewModel, + localSongViewModel: LocalSongViewModel, ) { val pagerState = rememberPagerState { 3 } val scope = rememberCoroutineScope() @@ -65,55 +153,55 @@ fun LocalMusicScreen( mutableStateOf(false) } - Column { + Column(modifier) { TabRow(selectedTabIndex = pagerState.currentPage, Modifier.fillMaxWidth()) { val view = LocalView.current Tab(selected = (pagerState.currentPage == 0), onClick = { view.playSoundEffect(SoundEffectConstants.CLICK) scope.launch { pagerState.animateScrollToPage( - 0 + 0, ) } }) { Text( stringResource(R.string.songs), Modifier.padding(10.dp), - style = MaterialTheme.typography.titleMedium + style = MaterialTheme.typography.titleMedium, ) } Tab(selected = (pagerState.currentPage == 1), onClick = { view.playSoundEffect(SoundEffectConstants.CLICK) scope.launch { pagerState.animateScrollToPage( - 1 + 1, ) } }) { Text( stringResource(R.string.albums), Modifier.padding(10.dp), - style = MaterialTheme.typography.titleMedium + style = MaterialTheme.typography.titleMedium, ) } Tab(selected = (pagerState.currentPage == 2), onClick = { view.playSoundEffect(SoundEffectConstants.CLICK) scope.launch { pagerState.animateScrollToPage( - 2 + 2, ) } }) { Text( stringResource(R.string.artists), Modifier.padding(10.dp), - style = MaterialTheme.typography.titleMedium + style = MaterialTheme.typography.titleMedium, ) } } HorizontalPager( state = pagerState, - modifier = Modifier.fillMaxSize() + modifier = Modifier.fillMaxSize(), ) { index -> when (index) { 0 -> { @@ -126,7 +214,7 @@ fun LocalMusicScreen( }) { Icon( imageVector = Icons.Rounded.PlayArrow, - contentDescription = stringResource(R.string.play_all) + contentDescription = stringResource(R.string.play_all), ) } @@ -138,7 +226,7 @@ fun LocalMusicScreen( }) { Icon( imageVector = Icons.Rounded.Shuffle, - contentDescription = stringResource(R.string.shuffle) + contentDescription = stringResource(R.string.shuffle), ) } } @@ -146,43 +234,48 @@ fun LocalMusicScreen( Column( Modifier .fillMaxSize() - .padding(innerPadding) + .padding(innerPadding), ) { Row( - modifier = Modifier + modifier = + Modifier .align(Alignment.End) .padding(top = 4.dp) .clip(RoundedCornerShape(12.dp)) .padding(horizontal = 10.dp, vertical = 6.dp) .clickable { showSortDialog = true - } + }, ) { Text(text = stringResource(R.string.sort_order)) Spacer(modifier = Modifier.width(6.dp)) - Icon(imageVector = Icons.Rounded.Sort, contentDescription = null) + Icon( + imageVector = Icons.AutoMirrored.Rounded.Sort, + contentDescription = null, + ) } SongListView( songs = localSongViewModel.songs, - sortOrder = localSongViewModel.songsSortOrder + sortOrder = localSongViewModel.songsSortOrder, + playerViewModel = playerViewModel, ) } } } - 1 -> AlbumList(items = localSongViewModel.albums, onClickCard = { - localSearchViewModel.getAlbumInfo(it) - onNavigate(Destination.LocalPlaylists) - }, onLongPress = {}) + 1 -> + AlbumList(items = localSongViewModel.albums, onClickCard = { + onNavigate(Destination.LocalPlaylists(it)) + }, onLongPress = {}) - 2 -> ArtistList( - items = localSongViewModel.artists, - onClickCard = { - localSearchViewModel.getArtistInfo(it) - onNavigate(Destination.LocalArtist) - }, - onLongPress = {} - ) + 2 -> + ArtistList( + items = localSongViewModel.artists, + onClickCard = { + onNavigate(Destination.LocalArtist(it)) + }, + onLongPress = {}, + ) } } } @@ -200,7 +293,7 @@ fun LocalMusicScreen( putString(Pref.latestSongsSortOrderKey, sortOrder.toString()) putBoolean(Pref.latestReverseSongsPrefKey, reverse) } - } + }, ) } } diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/dialogs/SortOrderDialog.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/localmusic/components/SortOrderDialog.kt similarity index 94% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/dialogs/SortOrderDialog.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/localmusic/components/SortOrderDialog.kt index 95712e00..131578e0 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/dialogs/SortOrderDialog.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/localmusic/components/SortOrderDialog.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.ui.dialogs +package app.suhasdissa.vibeyou.presentation.screens.localmusic.components import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -17,7 +17,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import app.suhasdissa.vibeyou.R -import app.suhasdissa.vibeyou.ui.components.ChipSelector +import app.suhasdissa.vibeyou.presentation.components.ChipSelector enum class SortOrder { Alphabetic, diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/LocalSongViewModel.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/localmusic/model/LocalSongViewModel.kt similarity index 89% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/LocalSongViewModel.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/localmusic/model/LocalSongViewModel.kt index 58297b29..a367cc78 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/LocalSongViewModel.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/localmusic/model/LocalSongViewModel.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.backend.viewmodel +package app.suhasdissa.vibeyou.presentation.screens.localmusic.model import android.util.Log import androidx.compose.runtime.getValue @@ -10,11 +10,11 @@ import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory import app.suhasdissa.vibeyou.MellowMusicApplication -import app.suhasdissa.vibeyou.backend.data.Album -import app.suhasdissa.vibeyou.backend.data.Artist -import app.suhasdissa.vibeyou.backend.data.Song import app.suhasdissa.vibeyou.backend.repository.LocalMusicRepository -import app.suhasdissa.vibeyou.ui.dialogs.SortOrder +import app.suhasdissa.vibeyou.domain.models.primary.Album +import app.suhasdissa.vibeyou.domain.models.primary.Artist +import app.suhasdissa.vibeyou.domain.models.primary.Song +import app.suhasdissa.vibeyou.presentation.screens.localmusic.components.SortOrder import app.suhasdissa.vibeyou.utils.Pref import kotlinx.coroutines.launch diff --git a/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/localsearch/LocalSearchScreen.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/localsearch/LocalSearchScreen.kt new file mode 100644 index 00000000..fa5476f9 --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/localsearch/LocalSearchScreen.kt @@ -0,0 +1,262 @@ +package app.suhasdissa.vibeyou.presentation.screens.localsearch + +import android.view.SoundEffectConstants +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.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +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.Clear +import androidx.compose.material.icons.rounded.Close +import androidx.compose.material.icons.rounded.History +import androidx.compose.material.icons.rounded.Search +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.ListItem +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SearchBar +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import app.suhasdissa.vibeyou.R +import app.suhasdissa.vibeyou.domain.models.primary.Song +import app.suhasdissa.vibeyou.navigation.Destination +import app.suhasdissa.vibeyou.presentation.components.ChipSelector +import app.suhasdissa.vibeyou.presentation.components.IllustratedMessageScreen +import app.suhasdissa.vibeyou.presentation.components.LoadingScreen +import app.suhasdissa.vibeyou.presentation.components.SongCard +import app.suhasdissa.vibeyou.presentation.components.SongList +import app.suhasdissa.vibeyou.presentation.components.SongSettingsSheetSearchPage +import app.suhasdissa.vibeyou.presentation.screens.album.components.AlbumList +import app.suhasdissa.vibeyou.presentation.screens.artist.components.ArtistList +import app.suhasdissa.vibeyou.presentation.screens.localsearch.model.LocalSearchViewModel +import app.suhasdissa.vibeyou.presentation.screens.onlinesearch.model.state.SearchState +import app.suhasdissa.vibeyou.presentation.screens.player.model.PlayerViewModel + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun LocalSearchScreen( + onNavigate: (Destination) -> Unit, + playerViewModel: PlayerViewModel, + localSearchViewModel: LocalSearchViewModel = viewModel(factory = LocalSearchViewModel.Factory) +) { + var isPopupOpen by remember { + mutableStateOf(localSearchViewModel.state !is SearchState.Success) + } + var showSongSettings by remember { mutableStateOf(false) } + var selectedSong by remember { mutableStateOf(null) } + val view = LocalView.current + Column(Modifier.fillMaxSize()) { + Row(modifier = Modifier.fillMaxWidth()) { + SearchBar( + modifier = Modifier + .weight(1f), + query = localSearchViewModel.search, + onQueryChange = { + localSearchViewModel.search = it + if (it.length > 3) localSearchViewModel.getSuggestions() + }, + onSearch = { + localSearchViewModel.searchPiped() + isPopupOpen = false + }, + placeholder = { + Text( + stringResource(id = R.string.search_songs) + ) + }, + active = isPopupOpen, + onActiveChange = { + isPopupOpen = it + }, + leadingIcon = { + if (isPopupOpen) { + IconButton(onClick = { isPopupOpen = false }) { + Icon( + imageVector = Icons.AutoMirrored.Rounded.ArrowBack, + contentDescription = stringResource(R.string.close_search) + ) + } + } else { + Icon( + modifier = Modifier.size(48.dp), + painter = painterResource(id = R.drawable.ic_launcher_foreground), + contentDescription = null + ) + } + }, + trailingIcon = { + if (isPopupOpen) { + IconButton(onClick = { + view.playSoundEffect(SoundEffectConstants.CLICK) + localSearchViewModel.search = "" + }) { + Icon( + Icons.Rounded.Clear, + contentDescription = stringResource(R.string.clear_search) + ) + } + } else { + Icon( + modifier = Modifier.padding(8.dp), + imageVector = Icons.Rounded.Search, + contentDescription = null + ) + } + } + ) { + LaunchedEffect(Unit) { + localSearchViewModel.setSearchHistory() + } + val scroll = rememberScrollState() + Column( + Modifier + .verticalScroll(scroll) + ) { + if (localSearchViewModel.songSearchSuggestion.isNotEmpty()) { + Text( + text = stringResource(R.string.songs), + style = MaterialTheme.typography.titleSmall + ) + localSearchViewModel.songSearchSuggestion.forEach { item -> + SongCard( + song = item, + onClickCard = { + playerViewModel.playSong(item) + }, + onLongPress = { + selectedSong = item + showSongSettings = true + } + ) + } + } + localSearchViewModel.history.forEach { + ListItem( + modifier = Modifier.clickable { + localSearchViewModel.search = it + localSearchViewModel.searchPiped() + isPopupOpen = false + }, + headlineContent = { Text(it) }, + leadingContent = { + Icon( + imageVector = Icons.Rounded.History, + contentDescription = null + ) + }, + trailingContent = { + IconButton( + modifier = Modifier.offset(x = 16.dp), + onClick = { + localSearchViewModel.deleteFromHistory(it) + } + ) { + Icon( + imageVector = Icons.Rounded.Close, + contentDescription = null + ) + } + } + ) + } + } + } + } + Column { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { + ChipSelector(onItemSelected = { + localSearchViewModel.searchFilter = it + localSearchViewModel.searchPiped() + }, defaultValue = localSearchViewModel.searchFilter) + } + when (val searchState = localSearchViewModel.state) { + is SearchState.Loading -> { + LoadingScreen() + } + + is SearchState.Error -> { + IllustratedMessageScreen( + image = R.drawable.ic_launcher_monochrome, + message = R.string.something_went_wrong + ) + } + + is SearchState.Success -> { + when (searchState) { + is SearchState.Success.Playlists -> { + AlbumList( + items = searchState.items, + onClickCard = { + onNavigate(Destination.LocalPlaylists(it)) + }, + onLongPress = { + } + ) + } + + is SearchState.Success.Songs -> { + SongList( + items = searchState.items, + onClickCard = { song -> + playerViewModel.playSong(song) + }, + onLongPress = { song -> + selectedSong = song + showSongSettings = true + } + ) + } + + is SearchState.Success.Artists -> { + ArtistList( + items = searchState.items, + onClickCard = { + onNavigate(Destination.LocalArtist(it)) + }, + onLongPress = { + } + ) + } + } + } + + is SearchState.Empty -> { + IllustratedMessageScreen( + image = R.drawable.ic_launcher_monochrome, + message = R.string.search_for_a_song, + messageColor = MaterialTheme.colorScheme.tertiary + ) + } + } + } + } + if (showSongSettings) { + selectedSong?.let { + SongSettingsSheetSearchPage( + onDismissRequest = { showSongSettings = false }, + song = selectedSong!!, + playerViewModel + ) + } + } +} diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/LocalSearchViewModel.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/localsearch/model/LocalSearchViewModel.kt similarity index 69% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/LocalSearchViewModel.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/localsearch/model/LocalSearchViewModel.kt index b1132418..cfdee7bb 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/LocalSearchViewModel.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/localsearch/model/LocalSearchViewModel.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.backend.viewmodel +package app.suhasdissa.vibeyou.presentation.screens.localsearch.model import android.os.Handler import android.os.Looper @@ -13,14 +13,10 @@ import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory import app.suhasdissa.vibeyou.MellowMusicApplication -import app.suhasdissa.vibeyou.backend.data.Album -import app.suhasdissa.vibeyou.backend.data.Artist -import app.suhasdissa.vibeyou.backend.data.Song import app.suhasdissa.vibeyou.backend.models.LocalSearchFilter import app.suhasdissa.vibeyou.backend.repository.LocalMusicRepository -import app.suhasdissa.vibeyou.backend.viewmodel.state.AlbumInfoState -import app.suhasdissa.vibeyou.backend.viewmodel.state.ArtistInfoState -import app.suhasdissa.vibeyou.backend.viewmodel.state.SearchState +import app.suhasdissa.vibeyou.domain.models.primary.Song +import app.suhasdissa.vibeyou.presentation.screens.onlinesearch.model.state.SearchState import kotlinx.coroutines.launch class LocalSearchViewModel(private val musicRepository: LocalMusicRepository) : ViewModel() { @@ -30,10 +26,6 @@ class LocalSearchViewModel(private val musicRepository: LocalMusicRepository) : var searchFilter = LocalSearchFilter.Songs var search by mutableStateOf("") var songSearchSuggestion: List by mutableStateOf(listOf()) - var albumInfoState: AlbumInfoState by mutableStateOf(AlbumInfoState.Loading) - private set - var artistInfoState: ArtistInfoState by mutableStateOf(ArtistInfoState.Loading) - private set fun getSuggestions() { if (search.length < 3) return @@ -58,36 +50,6 @@ class LocalSearchViewModel(private val musicRepository: LocalMusicRepository) : } } - fun getAlbumInfo(album: Album) { - viewModelScope.launch { - albumInfoState = AlbumInfoState.Loading - albumInfoState = try { - AlbumInfoState.Success( - album, - musicRepository.getAlbumInfo(album.id.toLong()) - ) - } catch (e: Exception) { - Log.e("Playlist Info", e.toString()) - AlbumInfoState.Error - } - } - } - - fun getArtistInfo(artist: Artist) { - viewModelScope.launch { - artistInfoState = ArtistInfoState.Loading - artistInfoState = try { - ArtistInfoState.Success( - artist, - musicRepository.getArtistInfo(artist.artistsText) - ) - } catch (e: Exception) { - Log.e("Artist Info", e.toString()) - ArtistInfoState.Error - } - } - } - fun searchPiped() { if (search.isEmpty()) return viewModelScope.launch { @@ -143,3 +105,4 @@ class LocalSearchViewModel(private val musicRepository: LocalMusicRepository) : } } } + diff --git a/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinemusic/MusicScreen.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinemusic/MusicScreen.kt new file mode 100644 index 00000000..828743fd --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinemusic/MusicScreen.kt @@ -0,0 +1,179 @@ +package app.suhasdissa.vibeyou.presentation.screens.onlinemusic + +import android.view.SoundEffectConstants +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Menu +import androidx.compose.material.icons.rounded.Search +import androidx.compose.material3.Card +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Tab +import androidx.compose.material3.TabRow +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import app.suhasdissa.vibeyou.R +import app.suhasdissa.vibeyou.navigation.Destination +import app.suhasdissa.vibeyou.presentation.screens.onlinemusic.components.SongsScreen +import app.suhasdissa.vibeyou.presentation.screens.player.model.PlayerViewModel +import app.suhasdissa.vibeyou.presentation.screens.playlists.PlaylistsScreen +import kotlinx.coroutines.launch + +@Composable +fun OnlineMusicScreen( + onNavigate: (Destination) -> Unit, + playerViewModel: PlayerViewModel, + onDrawerOpen: (() -> Unit)?, +) { + val view = LocalView.current + Scaffold(topBar = { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + if (onDrawerOpen != null) { + IconButton(onClick = { + view.playSoundEffect(SoundEffectConstants.CLICK) + onDrawerOpen() + }) { + Icon( + imageVector = Icons.Rounded.Menu, + contentDescription = "Open Navigation Drawer", + ) + } + } else { + Spacer(modifier = Modifier.width(8.dp)) + } + Card( + modifier = + Modifier + .fillMaxWidth() + .padding(vertical = 12.dp) + .padding(end = 8.dp) + .clickable { + view.playSoundEffect(SoundEffectConstants.CLICK) + onNavigate(Destination.OnlineSearch) + }, + shape = RoundedCornerShape(40), + ) { + Row( + modifier = + Modifier + .fillMaxWidth() + .padding(vertical = 2.dp) + .padding(end = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + modifier = Modifier.size(48.dp), + painter = painterResource(id = R.drawable.ic_launcher_foreground), + contentDescription = null, + ) + Text(stringResource(R.string.search_songs)) + Spacer(Modifier.weight(1f)) + Icon( + modifier = Modifier.padding(8.dp), + imageVector = Icons.Rounded.Search, + contentDescription = null, + ) + } + } + } + }) { pV -> + MusicScreenContent( + modifier = + Modifier + .consumeWindowInsets(pV) + .padding(pV), + onNavigate, + playerViewModel, + ) + } +} + +@Composable +private fun MusicScreenContent( + modifier: Modifier = Modifier, + onNavigate: (Destination) -> Unit, + playerViewModel: PlayerViewModel, +) { + val pagerState = rememberPagerState { 3 } + val scope = rememberCoroutineScope() + Column(modifier) { + TabRow(selectedTabIndex = pagerState.currentPage, Modifier.fillMaxWidth()) { + val view = LocalView.current + Tab(selected = (pagerState.currentPage == 0), onClick = { + view.playSoundEffect(SoundEffectConstants.CLICK) + scope.launch { + pagerState.animateScrollToPage( + 0, + ) + } + }) { + Text( + stringResource(R.string.songs), + Modifier.padding(10.dp), + style = MaterialTheme.typography.titleMedium, + ) + } + Tab(selected = (pagerState.currentPage == 1), onClick = { + view.playSoundEffect(SoundEffectConstants.CLICK) + scope.launch { + pagerState.animateScrollToPage( + 1, + ) + } + }) { + Text( + stringResource(R.string.favourite_songs), + Modifier.padding(10.dp), + style = MaterialTheme.typography.titleMedium, + ) + } + Tab(selected = (pagerState.currentPage == 2), onClick = { + view.playSoundEffect(SoundEffectConstants.CLICK) + scope.launch { + pagerState.animateScrollToPage( + 2, + ) + } + }) { + Text( + stringResource(R.string.playlists), + Modifier.padding(10.dp), + style = MaterialTheme.typography.titleMedium, + ) + } + } + HorizontalPager( + state = pagerState, + modifier = Modifier.fillMaxSize(), + ) { index -> + when (index) { + 0 -> SongsScreen(showFavourites = false, playerViewModel) + 1 -> SongsScreen(showFavourites = true, playerViewModel) + 2 -> PlaylistsScreen(onNavigate = onNavigate) + } + } + } +} diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/songs/SongsScreen.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinemusic/components/SongsScreen.kt similarity index 85% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/screens/songs/SongsScreen.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinemusic/components/SongsScreen.kt index 95d2427e..83b12930 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/songs/SongsScreen.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinemusic/components/SongsScreen.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.ui.screens.songs +package app.suhasdissa.vibeyou.presentation.screens.onlinemusic.components import android.view.SoundEffectConstants import androidx.compose.foundation.layout.Column @@ -21,14 +21,15 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import app.suhasdissa.vibeyou.R -import app.suhasdissa.vibeyou.backend.viewmodel.PlayerViewModel -import app.suhasdissa.vibeyou.backend.viewmodel.SongViewModel +import app.suhasdissa.vibeyou.presentation.components.SongListView +import app.suhasdissa.vibeyou.presentation.screens.onlinemusic.model.SongViewModel +import app.suhasdissa.vibeyou.presentation.screens.player.model.PlayerViewModel import app.suhasdissa.vibeyou.utils.asSong @Composable fun SongsScreen( showFavourites: Boolean, - playerViewModel: PlayerViewModel = viewModel(factory = PlayerViewModel.Factory), + playerViewModel: PlayerViewModel, songViewModel: SongViewModel = viewModel(factory = SongViewModel.Factory) ) { val view = LocalView.current @@ -66,7 +67,7 @@ fun SongsScreen( .fillMaxSize() .padding(innerPadding) ) { - SongListView(songs) + SongListView(songs, playerViewModel) } } } diff --git a/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinemusic/model/SongOptionsViewModel.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinemusic/model/SongOptionsViewModel.kt new file mode 100644 index 00000000..85f6969e --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinemusic/model/SongOptionsViewModel.kt @@ -0,0 +1,42 @@ +package app.suhasdissa.vibeyou.presentation.screens.onlinemusic.model + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory +import app.suhasdissa.vibeyou.MellowMusicApplication +import app.suhasdissa.vibeyou.backend.repository.SongDatabaseRepository +import app.suhasdissa.vibeyou.domain.models.primary.Song +import kotlinx.coroutines.launch + +class SongOptionsViewModel(private val songDatabaseRepository: SongDatabaseRepository) : + ViewModel() { + fun removeSong(song: Song) { + viewModelScope.launch { + songDatabaseRepository.removeSong(song) + } + } + + fun toggleFavourite(id: String) { + viewModelScope.launch { + val song = songDatabaseRepository.getSongById(id) + song ?: return@launch + song.let { + songDatabaseRepository.addSong(it.toggleLike()) + } + } + } + + companion object { + val Factory: ViewModelProvider.Factory = viewModelFactory { + initializer { + val application = + (this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as MellowMusicApplication) + SongOptionsViewModel( + application.container.songDatabaseRepository + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/SongViewModel.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinemusic/model/SongViewModel.kt similarity index 70% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/SongViewModel.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinemusic/model/SongViewModel.kt index 883957f1..dac70129 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/SongViewModel.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinemusic/model/SongViewModel.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.backend.viewmodel +package app.suhasdissa.vibeyou.presentation.screens.onlinemusic.model import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider @@ -6,11 +6,9 @@ import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory import app.suhasdissa.vibeyou.MellowMusicApplication -import app.suhasdissa.vibeyou.backend.data.Song import app.suhasdissa.vibeyou.backend.repository.SongDatabaseRepository import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch class SongViewModel(private val songDatabaseRepository: SongDatabaseRepository) : ViewModel() { val songs = songDatabaseRepository.getAllSongsStream().stateIn( @@ -25,22 +23,6 @@ class SongViewModel(private val songDatabaseRepository: SongDatabaseRepository) initialValue = listOf() ) - fun removeSong(song: Song) { - viewModelScope.launch { - songDatabaseRepository.removeSong(song) - } - } - - fun toggleFavourite(id: String) { - viewModelScope.launch { - val song = songDatabaseRepository.getSongById(id) - song ?: return@launch - song.let { - songDatabaseRepository.addSong(it.toggleLike()) - } - } - } - companion object { val Factory: ViewModelProvider.Factory = viewModelFactory { initializer { @@ -53,3 +35,4 @@ class SongViewModel(private val songDatabaseRepository: SongDatabaseRepository) } } } + diff --git a/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinesearch/SearchScreen.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinesearch/SearchScreen.kt new file mode 100644 index 00000000..a0810fa0 --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinesearch/SearchScreen.kt @@ -0,0 +1,285 @@ +package app.suhasdissa.vibeyou.presentation.screens.onlinesearch + +import android.view.SoundEffectConstants +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.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +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.Clear +import androidx.compose.material.icons.rounded.History +import androidx.compose.material.icons.rounded.Search +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.ListItem +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SearchBar +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import app.suhasdissa.vibeyou.R +import app.suhasdissa.vibeyou.domain.models.primary.Song +import app.suhasdissa.vibeyou.navigation.Destination +import app.suhasdissa.vibeyou.presentation.components.ChipSelector +import app.suhasdissa.vibeyou.presentation.components.IllustratedMessageScreen +import app.suhasdissa.vibeyou.presentation.components.LoadingScreen +import app.suhasdissa.vibeyou.presentation.components.SongCard +import app.suhasdissa.vibeyou.presentation.components.SongList +import app.suhasdissa.vibeyou.presentation.components.SongSettingsSheetSearchPage +import app.suhasdissa.vibeyou.presentation.screens.album.components.AlbumList +import app.suhasdissa.vibeyou.presentation.screens.artist.components.ArtistList +import app.suhasdissa.vibeyou.presentation.screens.onlinesearch.model.PipedSearchViewModel +import app.suhasdissa.vibeyou.presentation.screens.onlinesearch.model.state.SearchState +import app.suhasdissa.vibeyou.presentation.screens.player.model.PlayerViewModel + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SearchScreen( + onNavigate: (Destination) -> Unit, + playerViewModel: PlayerViewModel, + pipedSearchViewModel: PipedSearchViewModel = viewModel(factory = PipedSearchViewModel.Factory), +) { + var isPopupOpen by remember { + mutableStateOf(pipedSearchViewModel.state !is SearchState.Success) + } + var showSongSettings by remember { mutableStateOf(false) } + var selectedSong by remember { mutableStateOf(null) } + val view = LocalView.current + Column(Modifier.fillMaxSize()) { + Row(modifier = Modifier.fillMaxWidth()) { + SearchBar( + modifier = Modifier + .weight(1f), + query = pipedSearchViewModel.search, + onQueryChange = { + pipedSearchViewModel.search = it + if (it.length > 3) pipedSearchViewModel.getSuggestions() + }, + onSearch = { + pipedSearchViewModel.searchPiped() + isPopupOpen = false + }, + placeholder = { + Text( + stringResource(id = R.string.search_songs) + ) + }, + active = isPopupOpen, + onActiveChange = { + isPopupOpen = it + }, + leadingIcon = { + if (isPopupOpen) { + IconButton(onClick = { isPopupOpen = false }) { + Icon( + imageVector = Icons.AutoMirrored.Rounded.ArrowBack, + contentDescription = stringResource(R.string.close_search) + ) + } + } else { + Icon( + modifier = Modifier.size(48.dp), + painter = painterResource(id = R.drawable.ic_launcher_foreground), + contentDescription = null + ) + } + }, + trailingIcon = { + if (isPopupOpen) { + IconButton(onClick = { + view.playSoundEffect(SoundEffectConstants.CLICK) + pipedSearchViewModel.search = "" + }) { + Icon( + Icons.Rounded.Clear, + contentDescription = stringResource(R.string.clear_search) + ) + } + } else { + Icon( + modifier = Modifier.padding(8.dp), + imageVector = Icons.Rounded.Search, + contentDescription = null + ) + } + } + ) { + LaunchedEffect(Unit) { + pipedSearchViewModel.setSearchHistory() + } + val scroll = rememberScrollState() + Column( + Modifier + .verticalScroll(scroll) + .padding(horizontal = 8.dp) + ) { + if (pipedSearchViewModel.suggestions.isNotEmpty()) { + pipedSearchViewModel.suggestions.forEach { + ListItem( + modifier = Modifier.clickable { + pipedSearchViewModel.search = it + pipedSearchViewModel.searchPiped() + isPopupOpen = false + }, + headlineContent = { Text(it) }, + leadingContent = { + Icon( + imageVector = Icons.Rounded.Search, + contentDescription = null + ) + } + ) + } + } else { + pipedSearchViewModel.history.forEach { + ListItem( + modifier = Modifier.clickable { + pipedSearchViewModel.search = it + pipedSearchViewModel.searchPiped() + isPopupOpen = false + }, + headlineContent = { Text(it) }, + leadingContent = { + Icon( + imageVector = Icons.Rounded.History, + contentDescription = null + ) + } + ) + } + } + if (pipedSearchViewModel.songSearchSuggestion.isNotEmpty()) { + Text( + text = stringResource(R.string.recent_songs), + style = MaterialTheme.typography.titleSmall + ) + pipedSearchViewModel.songSearchSuggestion.forEach { item -> + SongCard( + song = item, + onClickCard = { + playerViewModel.playSong(item) + }, + onLongPress = { + selectedSong = item + showSongSettings = true + } + ) + } + } + } + } + } + Column { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { + ChipSelector(onItemSelected = { + pipedSearchViewModel.searchFilter = it + pipedSearchViewModel.searchPiped() + }, defaultValue = pipedSearchViewModel.searchFilter) + } + when (val searchState = pipedSearchViewModel.state) { + is SearchState.Loading -> { + LoadingScreen() + } + + is SearchState.Error -> { + IllustratedMessageScreen( + image = R.drawable.ic_launcher_monochrome, + message = R.string.something_went_wrong, + action = { + TextButton( + onClick = { + onNavigate(Destination.NetworkSettings) + }, + colors = ButtonDefaults.textButtonColors( + containerColor = MaterialTheme.colorScheme.errorContainer, + contentColor = MaterialTheme.colorScheme.onErrorContainer + ) + ) { + Text(stringResource(id = R.string.network_settings)) + } + } + ) + } + + is SearchState.Success -> { + when (searchState) { + is SearchState.Success.Playlists -> { + AlbumList( + items = searchState.items, + onClickCard = { + onNavigate(Destination.Playlists(it)) + }, + onLongPress = { + } + ) + } + + is SearchState.Success.Songs -> { + SongList( + items = searchState.items, + onClickCard = { song -> + playerViewModel.playSong(song) + if (!song.isLocal) { + playerViewModel.saveSong(song) + } + }, + onLongPress = { song -> + selectedSong = song + showSongSettings = true + } + ) + } + + is SearchState.Success.Artists -> { + ArtistList( + items = searchState.items, + onClickCard = { + onNavigate(Destination.OnlineArtist(it)) + }, + onLongPress = { + } + ) + } + } + } + + is SearchState.Empty -> { + IllustratedMessageScreen( + image = R.drawable.ic_launcher_monochrome, + message = R.string.search_for_a_song, + messageColor = MaterialTheme.colorScheme.tertiary + ) + } + } + } + } + if (showSongSettings) { + selectedSong?.let { + SongSettingsSheetSearchPage( + onDismissRequest = { showSongSettings = false }, + song = selectedSong!!, + playerViewModel + ) + } + } +} diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/PipedSearchViewModel.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinesearch/model/PipedSearchViewModel.kt similarity index 66% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/PipedSearchViewModel.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinesearch/model/PipedSearchViewModel.kt index 84a28275..0ba7b1ab 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/PipedSearchViewModel.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinesearch/model/PipedSearchViewModel.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.backend.viewmodel +package app.suhasdissa.vibeyou.presentation.screens.onlinesearch.model import android.os.Handler import android.os.Looper @@ -6,7 +6,6 @@ import android.util.Log import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import androidx.core.net.toUri import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY @@ -14,15 +13,10 @@ import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory import app.suhasdissa.vibeyou.MellowMusicApplication -import app.suhasdissa.vibeyou.backend.data.Album -import app.suhasdissa.vibeyou.backend.data.Artist -import app.suhasdissa.vibeyou.backend.data.Song import app.suhasdissa.vibeyou.backend.models.SearchFilter import app.suhasdissa.vibeyou.backend.repository.PipedMusicRepository -import app.suhasdissa.vibeyou.backend.viewmodel.state.AlbumInfoState -import app.suhasdissa.vibeyou.backend.viewmodel.state.ArtistInfoState -import app.suhasdissa.vibeyou.backend.viewmodel.state.SearchState -import app.suhasdissa.vibeyou.utils.asSong +import app.suhasdissa.vibeyou.domain.models.primary.Song +import app.suhasdissa.vibeyou.presentation.screens.onlinesearch.model.state.SearchState import kotlinx.coroutines.launch class PipedSearchViewModel(private val musicRepository: PipedMusicRepository) : ViewModel() { @@ -33,10 +27,6 @@ class PipedSearchViewModel(private val musicRepository: PipedMusicRepository) : var searchFilter = SearchFilter.Songs var search by mutableStateOf("") var songSearchSuggestion: List by mutableStateOf(listOf()) - var albumInfoState: AlbumInfoState by mutableStateOf(AlbumInfoState.Loading) - private set - var artistInfoState: ArtistInfoState by mutableStateOf(ArtistInfoState.Loading) - private set fun getSuggestions() { if (search.length < 3) return @@ -59,42 +49,6 @@ class PipedSearchViewModel(private val musicRepository: PipedMusicRepository) : ) } - fun getPlaylistInfo(playlist: Album) { - viewModelScope.launch { - albumInfoState = AlbumInfoState.Loading - albumInfoState = try { - val info = musicRepository.getPlaylistInfo(playlist.id) - AlbumInfoState.Success( - playlist, - info.relatedStreams.map { it.asSong } - ) - } catch (e: Exception) { - Log.e("Playlist Info", e.toString()) - AlbumInfoState.Error - } - } - } - - fun getChannelInfo(artist: Artist) { - viewModelScope.launch { - artistInfoState = ArtistInfoState.Loading - artistInfoState = try { - val info = musicRepository.getChannelInfo(artist.id) - val playlists = musicRepository.getChannelPlaylists(artist.id, info.tabs) - ArtistInfoState.Success( - artist.copy( - thumbnailUri = info.avatarUrl?.toUri(), - description = info.description - ), - playlists - ) - } catch (e: Exception) { - Log.e("Playlist Info", e.toString()) - ArtistInfoState.Error - } - } - } - fun setSearchHistory() { viewModelScope.launch { history = musicRepository.getSearchHistory().takeLast(6).reversed().map { it.query } @@ -158,4 +112,4 @@ class PipedSearchViewModel(private val musicRepository: PipedMusicRepository) : } } } -} +} \ No newline at end of file diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/state/AlbumInfoState.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinesearch/model/state/AlbumInfoState.kt similarity index 52% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/state/AlbumInfoState.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinesearch/model/state/AlbumInfoState.kt index 5b03bd03..195c0242 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/state/AlbumInfoState.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinesearch/model/state/AlbumInfoState.kt @@ -1,7 +1,7 @@ -package app.suhasdissa.vibeyou.backend.viewmodel.state +package app.suhasdissa.vibeyou.presentation.screens.onlinesearch.model.state -import app.suhasdissa.vibeyou.backend.data.Album -import app.suhasdissa.vibeyou.backend.data.Song +import app.suhasdissa.vibeyou.domain.models.primary.Album +import app.suhasdissa.vibeyou.domain.models.primary.Song sealed interface AlbumInfoState { object Loading : AlbumInfoState diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/state/ArtistInfoState.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinesearch/model/state/ArtistInfoState.kt similarity index 53% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/state/ArtistInfoState.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinesearch/model/state/ArtistInfoState.kt index 2e94f30d..33df2db4 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/state/ArtistInfoState.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinesearch/model/state/ArtistInfoState.kt @@ -1,7 +1,7 @@ -package app.suhasdissa.vibeyou.backend.viewmodel.state +package app.suhasdissa.vibeyou.presentation.screens.onlinesearch.model.state -import app.suhasdissa.vibeyou.backend.data.Album -import app.suhasdissa.vibeyou.backend.data.Artist +import app.suhasdissa.vibeyou.domain.models.primary.Album +import app.suhasdissa.vibeyou.domain.models.primary.Artist sealed interface ArtistInfoState { object Loading : ArtistInfoState diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/state/SearchState.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinesearch/model/state/SearchState.kt similarity index 60% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/state/SearchState.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinesearch/model/state/SearchState.kt index 804791a6..3ec24a0c 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/state/SearchState.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/onlinesearch/model/state/SearchState.kt @@ -1,8 +1,8 @@ -package app.suhasdissa.vibeyou.backend.viewmodel.state +package app.suhasdissa.vibeyou.presentation.screens.onlinesearch.model.state -import app.suhasdissa.vibeyou.backend.data.Album -import app.suhasdissa.vibeyou.backend.data.Artist -import app.suhasdissa.vibeyou.backend.data.Song +import app.suhasdissa.vibeyou.domain.models.primary.Album +import app.suhasdissa.vibeyou.domain.models.primary.Artist +import app.suhasdissa.vibeyou.domain.models.primary.Song sealed interface SearchState { sealed interface Success : SearchState { diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/player/FullScreenPlayer.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/player/FullScreenPlayer.kt similarity index 70% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/screens/player/FullScreenPlayer.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/player/FullScreenPlayer.kt index 3e1ee70a..fd7e80d7 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/player/FullScreenPlayer.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/player/FullScreenPlayer.kt @@ -1,7 +1,8 @@ -package app.suhasdissa.vibeyou.ui.screens.player +package app.suhasdissa.vibeyou.presentation.screens.player import android.text.format.DateUtils import android.view.SoundEffectConstants +import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -12,16 +13,17 @@ 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.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.rounded.QueueMusic import androidx.compose.material.icons.rounded.ExpandMore import androidx.compose.material.icons.rounded.Favorite import androidx.compose.material.icons.rounded.FavoriteBorder import androidx.compose.material.icons.rounded.MoreVert import androidx.compose.material.icons.rounded.Pause import androidx.compose.material.icons.rounded.PlayArrow -import androidx.compose.material.icons.rounded.QueueMusic import androidx.compose.material.icons.rounded.Repeat import androidx.compose.material.icons.rounded.RepeatOn import androidx.compose.material.icons.rounded.RepeatOneOn @@ -30,9 +32,9 @@ import androidx.compose.material.icons.rounded.SkipPrevious import androidx.compose.material3.CardDefaults import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.Divider import androidx.compose.material3.ElevatedCard import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -56,16 +58,18 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.media3.common.MediaItem import androidx.media3.session.MediaController import app.suhasdissa.vibeyou.R import app.suhasdissa.vibeyou.backend.models.PlayerRepeatMode import app.suhasdissa.vibeyou.backend.models.PlayerState -import app.suhasdissa.vibeyou.backend.viewmodel.PlayerViewModel +import app.suhasdissa.vibeyou.presentation.screens.player.model.PlayerViewModel import app.suhasdissa.vibeyou.utils.IS_LOCAL_KEY import app.suhasdissa.vibeyou.utils.isPlayingState import app.suhasdissa.vibeyou.utils.maxResThumbnail import app.suhasdissa.vibeyou.utils.mediaItemState import app.suhasdissa.vibeyou.utils.positionAndDurationState +import coil.compose.AsyncImagePainter import coil.compose.SubcomposeAsyncImage import coil.compose.SubcomposeAsyncImageContent @@ -73,29 +77,30 @@ import coil.compose.SubcomposeAsyncImageContent @Composable fun FullScreenPlayer( controller: MediaController, - onCollapse: () -> Unit, - playerViewModel: PlayerViewModel = viewModel(factory = PlayerViewModel.Factory) + onCollapse: (() -> Unit)?, + playerViewModel: PlayerViewModel, + onClickShowSongOptions: () -> Unit, + onClickShowQueueSheet: () -> Unit ) { - var showQueueSheet by remember { mutableStateOf(false) } - var showSongOptions by remember { mutableStateOf(false) } - val view = LocalView.current CenterAlignedTopAppBar(navigationIcon = { - IconButton({ - view.playSoundEffect(SoundEffectConstants.CLICK) - onCollapse.invoke() - }) { - Icon( - Icons.Rounded.ExpandMore, - contentDescription = stringResource(R.string.close_player) - ) + onCollapse?.let { onCollapse -> + IconButton({ + view.playSoundEffect(SoundEffectConstants.CLICK) + onCollapse.invoke() + }) { + Icon( + Icons.Rounded.ExpandMore, + contentDescription = stringResource(R.string.close_player) + ) + } } }, title = { Text(stringResource(R.string.now_playing)) }, actions = { - IconButton(onClick = { showSongOptions = true }) { + IconButton(onClick = onClickShowSongOptions) { Icon(Icons.Rounded.MoreVert, contentDescription = stringResource(R.string.song_options)) } }) - Divider(Modifier.fillMaxWidth()) + HorizontalDivider(Modifier.fillMaxWidth()) Column( Modifier .fillMaxSize(), @@ -116,30 +121,14 @@ fun FullScreenPlayer( isFavourite.value = playerViewModel.isFavourite(mediaItem!!.mediaId) } } - var thumbnailUrl by remember { - mutableStateOf(it.maxResThumbnail) - } - SubcomposeAsyncImage( + AlbumArtBox( modifier = Modifier .size(300.dp) .padding(8.dp) .aspectRatio(1f) .clip(RoundedCornerShape(16.dp)), - model = it.maxResThumbnail.ifEmpty { it.mediaMetadata.artworkUri }, - contentDescription = stringResource(id = R.string.album_art), - contentScale = ContentScale.Crop, - onError = { _ -> - if (thumbnailUrl != it.mediaMetadata.artworkUri.toString()) { - thumbnailUrl = it.mediaMetadata.artworkUri.toString() - } - }, - error = { - SubcomposeAsyncImageContent( - painter = painterResource(id = R.drawable.music_placeholder), - contentScale = contentScale - ) - } + it ) val title = it.mediaMetadata.title.toString() val artist = it.mediaMetadata.artist.toString() @@ -173,18 +162,134 @@ fun FullScreenPlayer( ) { IconButton(onClick = { view.playSoundEffect(SoundEffectConstants.CLICK) - showQueueSheet = true + onClickShowQueueSheet.invoke() }) { - Icon(Icons.Rounded.QueueMusic, stringResource(R.string.show_queue)) + Icon(Icons.AutoMirrored.Rounded.QueueMusic, stringResource(R.string.show_queue)) + } + } + } +} + +@Composable +fun FullScreenPlayerHorizontal( + controller: MediaController, + playerViewModel: PlayerViewModel, + onClickShowQueueSheet: () -> Unit +) { + val view = LocalView.current + Row( + Modifier + .fillMaxSize(), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically + ) { + val mediaItem by controller.mediaItemState() + val isFavourite = remember { + mutableStateOf(false) + } + var isLocal by remember { + mutableStateOf(false) + } + mediaItem?.let { + LaunchedEffect(mediaItem) { + isLocal = mediaItem!!.mediaMetadata.extras?.getBoolean(IS_LOCAL_KEY) ?: false + if (!isLocal) { + isFavourite.value = playerViewModel.isFavourite(mediaItem!!.mediaId) + } + } + AlbumArtBox( + modifier = Modifier + .size(250.dp) + .padding(16.dp) + .aspectRatio(1f) + .clip(RoundedCornerShape(16.dp)), + it + ) + Spacer(modifier = Modifier.width(16.dp)) + Column(Modifier.weight(1f)) { + val title = it.mediaMetadata.title.toString() + val artist = it.mediaMetadata.artist.toString() + + Column( + Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + title, + style = MaterialTheme.typography.titleLarge, + textAlign = TextAlign.Center + ) + Spacer(Modifier.height(16.dp)) + Text( + artist, + style = MaterialTheme.typography.titleMedium, + textAlign = TextAlign.Center + ) + } + PlayerController( + isfavourite = isFavourite, + isLocal = isLocal, + onToggleFavourite = { playerViewModel.toggleFavourite(it.mediaId) } + ) + Row( + Modifier + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly + ) { + IconButton(onClick = { + view.playSoundEffect(SoundEffectConstants.CLICK) + onClickShowQueueSheet.invoke() + }) { + Icon( + Icons.AutoMirrored.Rounded.QueueMusic, + stringResource(R.string.show_queue) + ) + } + } + } + + } + } +} + +@Composable +private fun AlbumArtBox( + modifier: Modifier = Modifier, + mediaItem: MediaItem +) { + SubcomposeAsyncImage( + modifier = modifier, + model = mediaItem.maxResThumbnail, + contentDescription = stringResource(id = R.string.album_art), + contentScale = ContentScale.Crop + ) { + val maxResPainterState = painter.state + if (maxResPainterState !is AsyncImagePainter.State.Success) { + SubcomposeAsyncImage( + model = mediaItem.mediaMetadata.artworkUri, + contentDescription, + contentScale = contentScale + ) { + val lowResPainterState = painter.state + if (lowResPainterState !is AsyncImagePainter.State.Success) { + Image( + painter = painterResource(id = R.drawable.music_placeholder), + contentDescription, + contentScale = contentScale + ) + } else { + SubcomposeAsyncImageContent(contentScale = contentScale) + } } + } else { + SubcomposeAsyncImageContent(contentScale = contentScale) } } - if (showQueueSheet) QueueSheet(onDismissRequest = { showQueueSheet = false }) - if (showSongOptions) SongOptionsSheet(onDismissRequest = { showSongOptions = false }) } @Composable fun PlayerController( + modifier: Modifier = Modifier, playerViewModel: PlayerViewModel = viewModel(factory = PlayerViewModel.Factory), isfavourite: MutableState, isLocal: Boolean, @@ -192,7 +297,7 @@ fun PlayerController( ) { val view = LocalView.current playerViewModel.controller?.let { controller -> - Column(Modifier.padding(32.dp)) { + Column(modifier.padding(32.dp)) { val positionAndDuration by controller.positionAndDurationState() Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { Text(DateUtils.formatElapsedTime(positionAndDuration.first / 1000)) @@ -298,7 +403,7 @@ fun PlayerController( } } var repeatState by remember { - mutableStateOf(PlayerRepeatMode.values()[controller.repeatMode]) + mutableStateOf(PlayerRepeatMode.entries[controller.repeatMode]) } when (repeatState) { @@ -306,7 +411,7 @@ fun PlayerController( IconButton(onClick = { view.playSoundEffect(SoundEffectConstants.CLICK) repeatState = PlayerRepeatMode.ALL - controller.repeatMode = repeatState.mode + controller.repeatMode = PlayerRepeatMode.ALL.mode }) { Icon( Icons.Rounded.Repeat, @@ -319,7 +424,7 @@ fun PlayerController( IconButton(onClick = { view.playSoundEffect(SoundEffectConstants.CLICK) repeatState = PlayerRepeatMode.ONE - controller.repeatMode = repeatState.mode + controller.repeatMode = PlayerRepeatMode.ONE.mode }) { Icon( Icons.Rounded.RepeatOn, @@ -332,7 +437,7 @@ fun PlayerController( IconButton(onClick = { view.playSoundEffect(SoundEffectConstants.CLICK) repeatState = PlayerRepeatMode.OFF - controller.repeatMode = repeatState.mode + controller.repeatMode = PlayerRepeatMode.OFF.mode }) { Icon( Icons.Rounded.RepeatOneOn, diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/player/MiniPlayer.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/player/MiniPlayer.kt similarity index 87% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/screens/player/MiniPlayer.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/player/MiniPlayer.kt index 52edfb39..d49c505c 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/player/MiniPlayer.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/player/MiniPlayer.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.ui.screens.player +package app.suhasdissa.vibeyou.presentation.screens.player import android.view.SoundEffectConstants import androidx.compose.foundation.background @@ -6,10 +6,15 @@ 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.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Pause @@ -33,12 +38,11 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel import androidx.media3.common.MediaItem import androidx.media3.session.MediaController import app.suhasdissa.vibeyou.R import app.suhasdissa.vibeyou.backend.models.PlayerState -import app.suhasdissa.vibeyou.backend.viewmodel.PlayerViewModel +import app.suhasdissa.vibeyou.presentation.screens.player.model.PlayerViewModel import app.suhasdissa.vibeyou.utils.isPlayingState import app.suhasdissa.vibeyou.utils.positionAndDurationState import coil.compose.AsyncImage @@ -48,12 +52,14 @@ fun MiniPlayer( onClick: () -> Unit, controller: MediaController, mediaItem: MediaItem, - playerViewModel: PlayerViewModel = viewModel(factory = PlayerViewModel.Factory) + playerViewModel: PlayerViewModel, + windowInsets: WindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom), ) { val view = LocalView.current Row( Modifier .fillMaxWidth() + .windowInsetsPadding(windowInsets) .background(MaterialTheme.colorScheme.secondaryContainer) .clickable { view.playSoundEffect(SoundEffectConstants.CLICK) @@ -138,5 +144,8 @@ fun MiniPlayer( positionAndDuration.first.toFloat().div(duration) } ?: 0f - LinearProgressIndicator(modifier = Modifier.fillMaxWidth(), progress = progress) + LinearProgressIndicator( + progress = { progress }, + modifier = Modifier.fillMaxWidth(), + ) } diff --git a/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/player/components/EqualizerSheet.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/player/components/EqualizerSheet.kt new file mode 100644 index 00000000..2802834d --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/player/components/EqualizerSheet.kt @@ -0,0 +1,199 @@ +package app.suhasdissa.vibeyou.presentation.screens.player.components + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +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.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Button +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.Checkbox +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuDefaults +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.runtime.toMutableStateList +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.capitalize +import androidx.compose.ui.text.intl.Locale +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.core.content.edit +import app.suhasdissa.vibeyou.R +import app.suhasdissa.vibeyou.domain.models.primary.EqualizerData +import app.suhasdissa.vibeyou.presentation.components.VerticalSlider +import app.suhasdissa.vibeyou.presentation.screens.player.model.PlayerViewModel +import app.suhasdissa.vibeyou.utils.Pref +import app.suhasdissa.vibeyou.utils.rememberPreference + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun EqualizerSheet( + equalizerData: EqualizerData, + playerViewModel: PlayerViewModel, + onDismissRequest: () -> Unit +) { + ModalBottomSheet(onDismissRequest = onDismissRequest) { + CenterAlignedTopAppBar(title = { + Text(text = stringResource(id = R.string.equalizer), fontSize = 20.sp) + }) + HorizontalDivider(modifier = Modifier.fillMaxWidth()) + + var equalizerEnabled by rememberPreference(key = Pref.equalizerKey, defaultValue = false) + + Row(modifier = Modifier.clickable(interactionSource = remember { + MutableInteractionSource() + }, indication = null) { + equalizerEnabled = equalizerEnabled.not() + playerViewModel.updateEqualizerSettings() + }, verticalAlignment = Alignment.CenterVertically + ) { + Checkbox( + checked = equalizerEnabled, + onCheckedChange = { + equalizerEnabled = it + playerViewModel.updateEqualizerSettings() + } + ) + Text(text = stringResource(id = R.string.enabled)) + } + + AnimatedVisibility( + visible = equalizerEnabled, modifier = Modifier.padding(horizontal = 12.dp) + ) { + var selectedPresetIndex by remember { + mutableIntStateOf( + Pref.sharedPreferences.getInt(Pref.equalizerPresetKey, -1) + 1 + ) + } + + Column { + if (equalizerData.presets.isNotEmpty()) { + var expanded by remember { mutableStateOf(false) } + val options = listOf(stringResource(R.string.none)) + equalizerData.presets + + ExposedDropdownMenuBox( + expanded = expanded, + onExpandedChange = { expanded = it }, + ) { + TextField( + modifier = Modifier.menuAnchor(), + value = options[selectedPresetIndex].capitalize(Locale.current), + onValueChange = {}, + readOnly = true, + singleLine = true, + label = { Text(stringResource(id = R.string.equalizer_preset)) }, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, + colors = ExposedDropdownMenuDefaults.textFieldColors(), + ) + ExposedDropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + ) { + options.forEachIndexed { index, option -> + DropdownMenuItem( + text = { + Text( + text = option.capitalize(Locale.current), + style = MaterialTheme.typography.bodyLarge + ) + }, + onClick = { + selectedPresetIndex = index + + Pref.sharedPreferences.edit { + putInt(Pref.equalizerPresetKey, index - 1) + } + playerViewModel.updateEqualizerSettings() + + expanded = false + }, + contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding, + ) + } + } + } + } + + Spacer(modifier = Modifier.height(20.dp)) + + AnimatedVisibility(visible = selectedPresetIndex == 0) { + Column { + val bandLevels = remember { + val list = Pref.sharedPreferences.getString(Pref.equalizerBandsKey, "") + .takeIf { !it.isNullOrEmpty() }?.split(",")?.map(String::toShort) + // init at 0 - default/normal band volume + ?: List(equalizerData.bands.size) { (0).toShort() } + + list.toMutableStateList() + } + + fun onUpdateBands() { + Pref.sharedPreferences.edit { + putString( + Pref.equalizerBandsKey, bandLevels.joinToString(",") + ) + } + playerViewModel.updateEqualizerSettings() + } + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + equalizerData.bands.forEachIndexed { index, band -> + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + VerticalSlider( + modifier = Modifier.width(300.dp), + value = bandLevels[index].toFloat(), + onValueChange = { + bandLevels[index] = it.toInt().toShort() + }, + onValueChangeFinished = { + onUpdateBands() + }, + valueRange = band.minLevel.toFloat()..band.maxLevel.toFloat() + ) + Text(text = "${band.frequency / 1000}kHz") + } + } + } + + Spacer(modifier = Modifier.height(10.dp)) + + Button(onClick = { + for (i in bandLevels.indices) { + bandLevels[i] = 0 + } + onUpdateBands() + }) { + Text(text = stringResource(id = R.string.reset_equalizer)) + } + } + } + } + } + Spacer(modifier = Modifier.height(30.dp)) + } +} \ No newline at end of file diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/player/Queue.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/player/components/Queue.kt similarity index 87% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/screens/player/Queue.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/player/components/Queue.kt index 79e858a9..603ee9e0 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/player/Queue.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/player/components/Queue.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.ui.screens.player +package app.suhasdissa.vibeyou.presentation.screens.player.components import android.view.SoundEffectConstants import androidx.compose.animation.core.FastOutSlowInEasing @@ -28,7 +28,7 @@ import androidx.media3.common.MediaItem import androidx.media3.common.Player import androidx.media3.session.MediaController import app.suhasdissa.vibeyou.R -import app.suhasdissa.vibeyou.ui.components.SongCardCompact +import app.suhasdissa.vibeyou.presentation.components.SongCardCompact import app.suhasdissa.vibeyou.utils.DisposableListener import app.suhasdissa.vibeyou.utils.queue import org.burnoutcrew.reorderable.ReorderableItem @@ -68,6 +68,10 @@ fun Queue( val mediaItem = queue.second ReorderableItem(reorderableState = state, key = queue.first) { isDragging -> val elevation by animateDpAsState(if (isDragging) 16.dp else 0.dp) + Modifier + .shadow(elevation) + .detectReorderAfterLongPress(state) + .background(MaterialTheme.colorScheme.surface) SongCardCompact( thumbnail = mediaItem.mediaMetadata.artworkUri, title = mediaItem.mediaMetadata.title.toString(), @@ -90,16 +94,12 @@ fun Queue( view.playSoundEffect(SoundEffectConstants.CLICK) controller.seekTo(queue.first, 0L) }, - modifier = Modifier - .shadow(elevation) - .detectReorderAfterLongPress(state) - .background(MaterialTheme.colorScheme.surface) - .animateItemPlacement( - animationSpec = tween( - durationMillis = 100, - easing = FastOutSlowInEasing - ) + modifier = Modifier.animateItem( + fadeInSpec = null, fadeOutSpec = null, placementSpec = tween( + durationMillis = 100, + easing = FastOutSlowInEasing ) + ) ) } } diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/player/QueueSheet.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/player/components/QueueSheet.kt similarity index 90% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/screens/player/QueueSheet.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/player/components/QueueSheet.kt index e2cfb78f..298271eb 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/player/QueueSheet.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/player/components/QueueSheet.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.ui.screens.player +package app.suhasdissa.vibeyou.presentation.screens.player.components import android.view.SoundEffectConstants import androidx.compose.foundation.layout.fillMaxWidth @@ -7,10 +7,10 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.ExpandMore import androidx.compose.material.icons.rounded.MoreVert import androidx.compose.material3.CenterAlignedTopAppBar -import androidx.compose.material3.Divider import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.ModalBottomSheet @@ -26,16 +26,15 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel import app.suhasdissa.vibeyou.R -import app.suhasdissa.vibeyou.backend.viewmodel.PlayerViewModel +import app.suhasdissa.vibeyou.presentation.screens.player.model.PlayerViewModel import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @Composable fun QueueSheet( onDismissRequest: () -> Unit, - playerViewModel: PlayerViewModel = viewModel(factory = PlayerViewModel.Factory) + playerViewModel: PlayerViewModel ) { val playerSheetState = rememberModalBottomSheetState( skipPartiallyExpanded = true @@ -87,7 +86,7 @@ fun QueueSheet( }) } }) - Divider(Modifier.fillMaxWidth()) + HorizontalDivider(Modifier.fillMaxWidth()) playerViewModel.controller?.let { controller -> Queue(controller) } diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/player/SongOptionsSheet.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/player/components/SongOptionsSheet.kt similarity index 80% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/screens/player/SongOptionsSheet.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/player/components/SongOptionsSheet.kt index c569a71e..481bdcc7 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/player/SongOptionsSheet.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/player/components/SongOptionsSheet.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.ui.screens.player +package app.suhasdissa.vibeyou.presentation.screens.player.components import android.view.SoundEffectConstants import androidx.compose.foundation.layout.Column @@ -11,10 +11,11 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.ExpandMore +import androidx.compose.material3.Button import androidx.compose.material3.CenterAlignedTopAppBar -import androidx.compose.material3.Divider import androidx.compose.material3.ElevatedCard import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.ModalBottomSheet @@ -29,26 +30,30 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.lifecycle.viewmodel.compose.viewModel +import app.suhasdissa.vibeyou.MellowMusicApplication import app.suhasdissa.vibeyou.R -import app.suhasdissa.vibeyou.backend.viewmodel.PlayerViewModel +import app.suhasdissa.vibeyou.presentation.screens.player.model.PlayerViewModel import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @Composable fun SongOptionsSheet( onDismissRequest: () -> Unit, - playerViewModel: PlayerViewModel = viewModel(factory = PlayerViewModel.Factory) + playerViewModel: PlayerViewModel, + onClickShowEqualizer: () -> Unit ) { val playerSheetState = rememberModalBottomSheetState( skipPartiallyExpanded = true ) val scope = rememberCoroutineScope() val view = LocalView.current + val app = LocalContext.current.applicationContext as MellowMusicApplication + ModalBottomSheet( onDismissRequest = onDismissRequest, sheetState = playerSheetState, @@ -66,12 +71,11 @@ fun SongOptionsSheet( } }) { Icon( - Icons.Rounded.ExpandMore, - contentDescription = null + Icons.Rounded.ExpandMore, contentDescription = null ) } }, title = { Text(stringResource(R.string.song_options)) }) - Divider(Modifier.fillMaxWidth()) + HorizontalDivider(Modifier.fillMaxWidth()) val scrollState = rememberScrollState() Column( @@ -85,6 +89,7 @@ fun SongOptionsSheet( var pitch by remember { mutableFloatStateOf(playerViewModel.getPlaybackParams().pitch) } + fun updatePlaybackParams() = playerViewModel.setPlaybackParams(speed, pitch) Text(text = stringResource(R.string.playback_speed), fontSize = 16.sp) @@ -110,6 +115,14 @@ fun SongOptionsSheet( ) } } - Spacer(modifier = Modifier.height(20.dp)) + Spacer(modifier = Modifier.height(10.dp)) + if (app.supportedEqualizerData != null) { + Button(modifier = Modifier.padding(start = 10.dp), onClick = { + onClickShowEqualizer.invoke() + }) { + Text(text = stringResource(id = R.string.equalizer)) + } + } + Spacer(modifier = Modifier.height(30.dp)) } } diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/PlayerViewModel.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/player/model/PlayerViewModel.kt similarity index 78% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/PlayerViewModel.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/player/model/PlayerViewModel.kt index 6d81b673..51fa3f20 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/PlayerViewModel.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/player/model/PlayerViewModel.kt @@ -1,5 +1,7 @@ -package app.suhasdissa.vibeyou.backend.viewmodel +package app.suhasdissa.vibeyou.presentation.screens.player.model +import android.net.Uri +import android.os.Bundle import android.os.Handler import android.os.Looper import android.util.Log @@ -15,10 +17,12 @@ import androidx.lifecycle.viewmodel.viewModelFactory import androidx.media3.common.MediaItem import androidx.media3.common.PlaybackParameters import androidx.media3.session.MediaController +import androidx.media3.session.SessionCommand import app.suhasdissa.vibeyou.MellowMusicApplication -import app.suhasdissa.vibeyou.backend.data.Song +import app.suhasdissa.vibeyou.backend.repository.LocalMusicRepository import app.suhasdissa.vibeyou.backend.repository.PipedMusicRepository import app.suhasdissa.vibeyou.backend.repository.SongDatabaseRepository +import app.suhasdissa.vibeyou.domain.models.primary.Song import app.suhasdissa.vibeyou.utils.addNext import app.suhasdissa.vibeyou.utils.asMediaItem import app.suhasdissa.vibeyou.utils.enqueue @@ -26,6 +30,9 @@ import app.suhasdissa.vibeyou.utils.forcePlay import app.suhasdissa.vibeyou.utils.forcePlayFromBeginning import app.suhasdissa.vibeyou.utils.playGracefully import app.suhasdissa.vibeyou.utils.playPause +import app.suhasdissa.vibeyou.utils.seek +import app.suhasdissa.vibeyou.utils.seekNext +import app.suhasdissa.vibeyou.utils.services.PlayerService import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.MoreExecutors import kotlinx.coroutines.launch @@ -33,6 +40,7 @@ import kotlinx.coroutines.launch class PlayerViewModel( private val songDatabaseRepository: SongDatabaseRepository, private val musicRepository: PipedMusicRepository, + private val localMusicRepository: LocalMusicRepository, private val controllerFuture: ListenableFuture ) : ViewModel() { @@ -57,11 +65,11 @@ class PlayerViewModel( } fun seekNext() { - controller!!.seekToNext() + controller!!.seekNext() } fun seekTo(ms: Long) { - controller!!.seekTo(ms) + controller!!.seek(ms) } fun playPause() { @@ -129,6 +137,12 @@ class PlayerViewModel( } } + @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) + fun updateEqualizerSettings() { + val command = SessionCommand(PlayerService.COMMAND_UPDATE_EQUALIZER, Bundle.EMPTY) + controller!!.sendCustomCommand(command, Bundle.EMPTY) + } + suspend fun isFavourite(id: String): Boolean { val song = songDatabaseRepository.getSongById(id) song ?: return false @@ -148,6 +162,19 @@ class PlayerViewModel( } } + fun tryToPlayUri(uri: Uri) { + viewModelScope.launch { + val song: Song? = localMusicRepository.getSongFromUri(uri) + song?.let { + if (controller == null) { + toBePlayed = song.asMediaItem + } else { + controller?.forcePlay(song.asMediaItem) + } + } + } + } + companion object { val Factory: ViewModelProvider.Factory = viewModelFactory { initializer { @@ -155,6 +182,7 @@ class PlayerViewModel( PlayerViewModel( application.container.songDatabaseRepository, application.container.pipedMusicRepository, + application.container.localMusicRepository, application.container.controllerFuture ) } diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/playlists/PlaylistsScreen.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/playlists/PlaylistsScreen.kt similarity index 67% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/screens/playlists/PlaylistsScreen.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/playlists/PlaylistsScreen.kt index c0be1719..18f385ce 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/playlists/PlaylistsScreen.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/playlists/PlaylistsScreen.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.ui.screens.playlists +package app.suhasdissa.vibeyou.presentation.screens.playlists import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -7,10 +7,11 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.lifecycle.viewmodel.compose.viewModel -import app.suhasdissa.vibeyou.Destination -import app.suhasdissa.vibeyou.backend.data.Album -import app.suhasdissa.vibeyou.backend.viewmodel.PlaylistViewModel -import app.suhasdissa.vibeyou.ui.components.AlbumList +import app.suhasdissa.vibeyou.domain.models.primary.Album +import app.suhasdissa.vibeyou.navigation.Destination +import app.suhasdissa.vibeyou.presentation.screens.album.components.AlbumList +import app.suhasdissa.vibeyou.presentation.screens.playlists.components.PlaylistOptionsSheet +import app.suhasdissa.vibeyou.presentation.screens.playlists.model.PlaylistViewModel import app.suhasdissa.vibeyou.utils.asAlbum @Composable @@ -23,8 +24,7 @@ fun PlaylistsScreen( val albums by playlistViewModel.albums.collectAsState() var selectedAlbum: Album? by remember { mutableStateOf(null) } AlbumList(items = albums.map { it.asAlbum }, onClickCard = { - playlistViewModel.getPlaylistInfo(it) - onNavigate(Destination.SavedPlaylists) + onNavigate(Destination.SavedPlaylists(it)) }, onLongPress = { selectedAlbum = it }) diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/playlists/PlaylistOptionsSheet.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/playlists/components/PlaylistOptionsSheet.kt similarity index 93% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/screens/playlists/PlaylistOptionsSheet.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/playlists/components/PlaylistOptionsSheet.kt index 1d12995b..eb90bf5e 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/playlists/PlaylistOptionsSheet.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/playlists/components/PlaylistOptionsSheet.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.ui.screens.playlists +package app.suhasdissa.vibeyou.presentation.screens.playlists.components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -26,9 +26,9 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import app.suhasdissa.vibeyou.R -import app.suhasdissa.vibeyou.backend.data.Album -import app.suhasdissa.vibeyou.backend.viewmodel.PlaylistViewModel -import app.suhasdissa.vibeyou.ui.components.SheetSettingItem +import app.suhasdissa.vibeyou.domain.models.primary.Album +import app.suhasdissa.vibeyou.presentation.components.SheetSettingItem +import app.suhasdissa.vibeyou.presentation.screens.playlists.model.PlaylistViewModel import coil.compose.AsyncImage @OptIn(ExperimentalMaterial3Api::class) diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/PlaylistViewModel.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/playlists/model/PlaylistInfoViewModel.kt similarity index 53% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/PlaylistViewModel.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/playlists/model/PlaylistInfoViewModel.kt index e32df82a..67405a65 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/PlaylistViewModel.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/playlists/model/PlaylistInfoViewModel.kt @@ -1,35 +1,39 @@ -package app.suhasdissa.vibeyou.backend.viewmodel +package app.suhasdissa.vibeyou.presentation.screens.playlists.model import android.util.Log import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.createSavedStateHandle import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory +import androidx.navigation.toRoute import app.suhasdissa.vibeyou.MellowMusicApplication -import app.suhasdissa.vibeyou.backend.data.Album -import app.suhasdissa.vibeyou.backend.data.Song import app.suhasdissa.vibeyou.backend.repository.PlaylistRepository -import app.suhasdissa.vibeyou.backend.viewmodel.state.AlbumInfoState +import app.suhasdissa.vibeyou.domain.models.primary.Album +import app.suhasdissa.vibeyou.navigation.Destination +import app.suhasdissa.vibeyou.presentation.screens.onlinesearch.model.state.AlbumInfoState import app.suhasdissa.vibeyou.utils.asSong -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -class PlaylistViewModel(private val playlistRepository: PlaylistRepository) : ViewModel() { +class PlaylistInfoViewModel( + private val playlistRepository: PlaylistRepository, + savedStateHandle: SavedStateHandle +) : ViewModel() { + val playlist = savedStateHandle.toRoute() + var albumInfoState: AlbumInfoState by mutableStateOf(AlbumInfoState.Loading) private set - var albums = playlistRepository.getPlaylists().stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5000L), - initialValue = listOf() - ) + init { + getPlaylistInfo(playlist.album) + } - fun getPlaylistInfo(playlist: Album) { + private fun getPlaylistInfo(playlist: Album) { viewModelScope.launch { albumInfoState = AlbumInfoState.Loading albumInfoState = try { @@ -46,39 +50,16 @@ class PlaylistViewModel(private val playlistRepository: PlaylistRepository) : Vi } } - fun newPlaylistWithSongs(album: Album, songs: List) { - viewModelScope.launch { - playlistRepository.newPlaylistWithSongs(album, songs) - } - } - - fun deletePlaylist(album: Album) { - viewModelScope.launch { - playlistRepository.deletePlaylist(album) - } - } - - fun clearPlaylist(album: Album) { - viewModelScope.launch { - playlistRepository.clearPlaylist(album) - } - } - - fun deletePlaylistAndSongs(album: Album) { - viewModelScope.launch { - playlistRepository.deletePlaylistAndSongs(album) - } - } - companion object { val Factory: ViewModelProvider.Factory = viewModelFactory { initializer { val application = (this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as MellowMusicApplication) - PlaylistViewModel( - application.container.playlistRepository + PlaylistInfoViewModel( + application.container.playlistRepository, + this.createSavedStateHandle() ) } } } -} +} \ No newline at end of file diff --git a/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/playlists/model/PlaylistViewModel.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/playlists/model/PlaylistViewModel.kt new file mode 100644 index 00000000..b2567367 --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/playlists/model/PlaylistViewModel.kt @@ -0,0 +1,56 @@ +package app.suhasdissa.vibeyou.presentation.screens.playlists.model + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory +import app.suhasdissa.vibeyou.MellowMusicApplication +import app.suhasdissa.vibeyou.backend.repository.PlaylistRepository +import app.suhasdissa.vibeyou.domain.models.primary.Album +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch + +class PlaylistViewModel( + private val playlistRepository: PlaylistRepository +) : ViewModel() { + + + var albums = playlistRepository.getPlaylists().stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000L), + initialValue = listOf() + ) + + fun deletePlaylist(album: Album) { + viewModelScope.launch { + playlistRepository.deletePlaylist(album) + } + } + + fun clearPlaylist(album: Album) { + viewModelScope.launch { + playlistRepository.clearPlaylist(album) + } + } + + fun deletePlaylistAndSongs(album: Album) { + viewModelScope.launch { + playlistRepository.deletePlaylistAndSongs(album) + } + } + + companion object { + val Factory: ViewModelProvider.Factory = viewModelFactory { + initializer { + val application = + (this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as MellowMusicApplication) + PlaylistViewModel( + application.container.playlistRepository + ) + } + } + } +} + diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/settings/AboutScreen.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/AboutScreen.kt similarity index 81% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/screens/settings/AboutScreen.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/AboutScreen.kt index 2e1b9ba6..750456b6 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/settings/AboutScreen.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/AboutScreen.kt @@ -1,13 +1,18 @@ -package app.suhasdissa.vibeyou.ui.screens.settings +package app.suhasdissa.vibeyou.presentation.screens.settings -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.ContactSupport +import androidx.compose.material.icons.automirrored.rounded.ContactSupport import androidx.compose.material.icons.rounded.Description import androidx.compose.material.icons.rounded.Info import androidx.compose.material.icons.rounded.NewReleases -import androidx.compose.material3.* +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.LargeTopAppBar +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll @@ -16,8 +21,8 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.lifecycle.viewmodel.compose.viewModel import app.suhasdissa.vibeyou.R -import app.suhasdissa.vibeyou.backend.viewmodel.CheckUpdateViewModel -import app.suhasdissa.vibeyou.ui.components.SettingItem +import app.suhasdissa.vibeyou.presentation.screens.settings.components.SettingItem +import app.suhasdissa.vibeyou.presentation.screens.settings.model.CheckUpdateViewModel import app.suhasdissa.vibeyou.utils.openBrowser @OptIn(ExperimentalMaterial3Api::class) @@ -73,7 +78,7 @@ fun AboutScreen( "$githubRepo/issues" ) }, - icon = Icons.Rounded.ContactSupport + icon = Icons.AutoMirrored.Rounded.ContactSupport ) } item { diff --git a/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/AppearanceSettingsScreen.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/AppearanceSettingsScreen.kt new file mode 100644 index 00000000..8be5241c --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/AppearanceSettingsScreen.kt @@ -0,0 +1,89 @@ +package app.suhasdissa.vibeyou.presentation.screens.settings + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.LargeTopAppBar +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import app.suhasdissa.vibeyou.R +import app.suhasdissa.vibeyou.presentation.screens.settings.components.ButtonGroupPref +import app.suhasdissa.vibeyou.presentation.screens.settings.components.ColorPref +import app.suhasdissa.vibeyou.presentation.screens.settings.components.SwitchPref +import app.suhasdissa.vibeyou.presentation.screens.settings.model.SettingsModel +import app.suhasdissa.vibeyou.utils.Pref + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) +@Composable +fun AppearanceSettingsScreen(settingsModel: SettingsModel) { + val topBarBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + + Scaffold(modifier = Modifier.fillMaxSize(), topBar = { + LargeTopAppBar( + title = { Text(stringResource(R.string.appearance_settings)) }, + scrollBehavior = topBarBehavior + ) + }) { innerPadding -> + val state = rememberScrollState() + Column( + Modifier + .fillMaxSize() + .padding(innerPadding) + .nestedScroll(topBarBehavior.nestedScrollConnection) + .verticalScroll(state) + ) { + ButtonGroupPref( + title = stringResource(R.string.theme), + options = SettingsModel.Theme.values().map { + stringResource(it.resId) + }, + values = SettingsModel.Theme.values().toList(), + currentValue = settingsModel.themeMode + ) { + settingsModel.themeMode = it + } + ButtonGroupPref( + title = stringResource(R.string.color_scheme), + options = SettingsModel.ColorTheme.values().map { + stringResource(it.resId) + }, + values = SettingsModel.ColorTheme.values().toList(), + currentValue = settingsModel.colorTheme + ) { + settingsModel.colorTheme = it + } + AnimatedVisibility( + visible = settingsModel.colorTheme == SettingsModel.ColorTheme.CATPPUCCIN + ) { + ColorPref( + selectedColor = settingsModel.customColor, + onSelect = { + settingsModel.customColor = it + } + ) + } + HorizontalDivider( + modifier = Modifier.padding(top = 12.dp, bottom = 8.dp), + color = MaterialTheme.colorScheme.surfaceVariant + ) + SwitchPref( + prefKey = Pref.thumbnailColorFallbackKey, + title = stringResource(R.string.fallback_thumnail_accent), + summary = stringResource(R.string.fallback_thumnail_accent_description) + ) + } + } +} diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/settings/DatabaseSettingsScreen.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/DatabaseSettingsScreen.kt similarity index 94% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/screens/settings/DatabaseSettingsScreen.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/DatabaseSettingsScreen.kt index 363682b4..a99902a1 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/settings/DatabaseSettingsScreen.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/DatabaseSettingsScreen.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.ui.screens.settings +package app.suhasdissa.vibeyou.presentation.screens.settings import android.annotation.SuppressLint import android.app.Activity @@ -25,8 +25,9 @@ import androidx.compose.ui.res.stringResource import androidx.lifecycle.viewmodel.compose.viewModel import app.suhasdissa.vibeyou.MellowMusicApplication import app.suhasdissa.vibeyou.R -import app.suhasdissa.vibeyou.backend.viewmodel.DatabaseViewModel -import app.suhasdissa.vibeyou.ui.components.SettingItem +import app.suhasdissa.vibeyou.presentation.screens.settings.components.SettingItem +import app.suhasdissa.vibeyou.presentation.screens.settings.components.SwitchPref +import app.suhasdissa.vibeyou.presentation.screens.settings.model.DatabaseViewModel import app.suhasdissa.vibeyou.utils.Pref import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/settings/NetworkSettingsScreen.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/NetworkSettingsScreen.kt similarity index 77% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/screens/settings/NetworkSettingsScreen.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/NetworkSettingsScreen.kt index 91dba9e3..2a0abd50 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/settings/NetworkSettingsScreen.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/NetworkSettingsScreen.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.ui.screens.settings +package app.suhasdissa.vibeyou.presentation.screens.settings import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding @@ -21,9 +21,11 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.stringResource import androidx.lifecycle.viewmodel.compose.viewModel import app.suhasdissa.vibeyou.R -import app.suhasdissa.vibeyou.backend.viewmodel.SettingsViewModel -import app.suhasdissa.vibeyou.ui.components.InstanceSelectDialog -import app.suhasdissa.vibeyou.ui.components.SettingItem +import app.suhasdissa.vibeyou.presentation.screens.settings.components.CustomInstanceOption +import app.suhasdissa.vibeyou.presentation.screens.settings.components.InstanceSelectDialog +import app.suhasdissa.vibeyou.presentation.screens.settings.components.SettingItem +import app.suhasdissa.vibeyou.presentation.screens.settings.components.TextFieldPref +import app.suhasdissa.vibeyou.presentation.screens.settings.model.SettingsViewModel import app.suhasdissa.vibeyou.utils.Pref @OptIn(ExperimentalMaterial3Api::class) @@ -67,6 +69,14 @@ fun NetworkSettingsScreen() { currentServer = it } } + + item { + TextFieldPref( + key = Pref.hyperpipeApiUrlKey, + defaultValue = Pref.defaultHyperInstance, + title = stringResource(id = R.string.hyperpipe_api_url) + ) + } } } if (showDialog) { diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/settings/SettingsScreen.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/SettingsScreen.kt similarity index 77% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/screens/settings/SettingsScreen.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/SettingsScreen.kt index 155914e3..1560f3c0 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/settings/SettingsScreen.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/SettingsScreen.kt @@ -1,5 +1,6 @@ -package app.suhasdissa.vibeyou.ui.screens.settings +package app.suhasdissa.vibeyou.presentation.screens.settings +import android.view.SoundEffectConstants import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -8,10 +9,20 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Info import androidx.compose.material.icons.rounded.Landscape +import androidx.compose.material.icons.rounded.Menu import androidx.compose.material.icons.rounded.SettingsBackupRestore import androidx.compose.material.icons.rounded.Storage import androidx.compose.material.icons.rounded.Web -import androidx.compose.material3.* +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeTopAppBar +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -20,31 +31,47 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel -import app.suhasdissa.vibeyou.Destination import app.suhasdissa.vibeyou.R import app.suhasdissa.vibeyou.backend.models.Login import app.suhasdissa.vibeyou.backend.viewmodel.AuthViewModel -import app.suhasdissa.vibeyou.ui.components.CacheSizeDialog -import app.suhasdissa.vibeyou.ui.components.SettingItem +import app.suhasdissa.vibeyou.navigation.Destination +import app.suhasdissa.vibeyou.presentation.screens.settings.components.CacheSizeDialog +import app.suhasdissa.vibeyou.presentation.screens.settings.components.SettingItem @OptIn(ExperimentalMaterial3Api::class) @Composable fun SettingsScreen( modifier: Modifier = Modifier, - onNavigate: (route: String) -> Unit + onNavigate: (Destination) -> Unit, + onDrawerOpen: (() -> Unit)?, ) { var showLoginDialog by remember { mutableStateOf(false) } val authViewModel: AuthViewModel = viewModel(factory = AuthViewModel.Factory) val topBarBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() var showImageCacheDialog by remember { mutableStateOf(false) } + val view = LocalView.current Scaffold(modifier = modifier.fillMaxSize(), topBar = { LargeTopAppBar( title = { Text(stringResource(R.string.settings_title)) }, - scrollBehavior = topBarBehavior + scrollBehavior = topBarBehavior, + navigationIcon = { + if (onDrawerOpen != null) { + IconButton(onClick = { + view.playSoundEffect(SoundEffectConstants.CLICK) + onDrawerOpen() + }) { + Icon( + imageVector = Icons.Rounded.Menu, + contentDescription = "Open Navigation Drawer", + ) + } + } + } ) }) { innerPadding -> LazyColumn( @@ -58,7 +85,7 @@ fun SettingsScreen( SettingItem( title = stringResource(R.string.backup_restore), description = stringResource(R.string.backup_restore_setting_description), - onClick = { onNavigate(Destination.DatabaseSettings.route) }, + onClick = { onNavigate(Destination.DatabaseSettings) }, icon = Icons.Rounded.SettingsBackupRestore ) } @@ -66,7 +93,7 @@ fun SettingsScreen( SettingItem( title = stringResource(R.string.network_settings), description = stringResource(R.string.network_settings_description), - onClick = { onNavigate(Destination.NetworkSettings.route) }, + onClick = { onNavigate(Destination.NetworkSettings) }, icon = Icons.Rounded.Web ) } @@ -74,7 +101,7 @@ fun SettingsScreen( SettingItem( title = stringResource(R.string.appearance_settings), description = stringResource(R.string.appearance_settings_description), - onClick = { onNavigate(Destination.AppearanceSettings.route) }, + onClick = { onNavigate(Destination.AppearanceSettings) }, icon = Icons.Rounded.Landscape ) } @@ -102,7 +129,7 @@ fun SettingsScreen( SettingItem( title = stringResource(R.string.about_title), description = stringResource(R.string.about_setting_description), - onClick = { onNavigate(Destination.About.route) }, + onClick = { onNavigate(Destination.About) }, icon = Icons.Outlined.Info ) } diff --git a/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/ButtonGroupPref.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/ButtonGroupPref.kt new file mode 100644 index 00000000..e259cd8e --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/ButtonGroupPref.kt @@ -0,0 +1,94 @@ +package app.suhasdissa.vibeyou.presentation.screens.settings.components + +import androidx.compose.foundation.BorderStroke +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.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +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.Modifier +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex + +@Composable +fun ButtonGroupPref( + title: String, + options: List, + values: List, + currentValue: T, + onChange: (T) -> Unit +) { + Column( + modifier = Modifier + .padding(top = 8.dp) + .padding(horizontal = 12.dp) + ) { + Text(title) + Spacer(modifier = Modifier.height(8.dp)) + Row( + modifier = Modifier + .fillMaxWidth() + ) { + val cornerRadius = 20.dp + var selectedItem by remember { + mutableStateOf( + currentValue + ) + } + + values.forEachIndexed { index, value -> + val startRadius = if (index != 0) 0.dp else cornerRadius + val endRadius = if (index == values.size - 1) cornerRadius else 0.dp + + OutlinedButton( + onClick = { + selectedItem = value + onChange.invoke(values[index]) + }, + modifier = Modifier + .offset(if (index == 0) 0.dp else (-1 * index).dp, 0.dp) + .zIndex(if (selectedItem == value) 1f else 0f), + shape = RoundedCornerShape( + topStart = startRadius, + topEnd = endRadius, + bottomStart = startRadius, + bottomEnd = endRadius + ), + border = BorderStroke( + 1.dp, + if (selectedItem == value) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.primary.copy(alpha = 0.75f) + } + ), + colors = if (selectedItem == value) { + ButtonDefaults.outlinedButtonColors( + containerColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.2f), + contentColor = MaterialTheme.colorScheme.primary + ) + } else { + ButtonDefaults.outlinedButtonColors( + containerColor = MaterialTheme.colorScheme.surface, + contentColor = MaterialTheme.colorScheme.onSurface + ) + } + ) { + Text(options[index]) + } + } + } + } +} diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/CacheSizeDialog.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/CacheSizeDialog.kt similarity index 97% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/components/CacheSizeDialog.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/CacheSizeDialog.kt index ba8894a8..1de1ff4c 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/CacheSizeDialog.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/CacheSizeDialog.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.ui.components +package app.suhasdissa.vibeyou.presentation.screens.settings.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.lazy.grid.GridCells diff --git a/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/ColorPref.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/ColorPref.kt new file mode 100644 index 00000000..d1d15207 --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/ColorPref.kt @@ -0,0 +1,48 @@ +package app.suhasdissa.vibeyou.presentation.screens.settings.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Check +import androidx.compose.material3.Icon +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.graphics.Color +import androidx.compose.ui.unit.dp +import app.suhasdissa.vibeyou.utils.catpucchinLatte + +@Composable +fun ColorPref(selectedColor: Int, onSelect: (Int) -> Unit) { + LazyRow( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp) + .padding(horizontal = 12.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + items(catpucchinLatte) { color -> + Box( + modifier = Modifier + .size(48.dp) + .clip(CircleShape) + .background(Color(color)) + .clickable { onSelect(color) }, + contentAlignment = Alignment.Center + ) { + if (color == selectedColor) { + Icon(imageVector = Icons.Rounded.Check, contentDescription = null) + } + } + } + } +} diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/settings/CustomInstanceOption.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/CustomInstanceOption.kt similarity index 98% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/screens/settings/CustomInstanceOption.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/CustomInstanceOption.kt index 0535c361..951fee3e 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/settings/CustomInstanceOption.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/CustomInstanceOption.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.ui.screens.settings +package app.suhasdissa.vibeyou.presentation.screens.settings.components import android.widget.Toast import androidx.compose.foundation.layout.Column diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/InstanceSelectDialog.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/InstanceSelectDialog.kt similarity index 94% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/components/InstanceSelectDialog.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/InstanceSelectDialog.kt index 17f4a6ea..a1bd9a24 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/InstanceSelectDialog.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/InstanceSelectDialog.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.ui.components +package app.suhasdissa.vibeyou.presentation.screens.settings.components import android.view.SoundEffectConstants import androidx.compose.foundation.clickable @@ -29,7 +29,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import app.suhasdissa.vibeyou.R import app.suhasdissa.vibeyou.backend.models.PipedInstance -import app.suhasdissa.vibeyou.backend.viewmodel.SettingsViewModel +import app.suhasdissa.vibeyou.presentation.screens.settings.model.SettingsViewModel import app.suhasdissa.vibeyou.utils.Pref @Composable @@ -64,13 +64,13 @@ fun InstanceSelectDialog( Row( Modifier .fillMaxWidth() + .clip(RoundedCornerShape(12.dp)) .clickable(onClick = { view.playSoundEffect(SoundEffectConstants.CLICK) selectedInstance = item onSelectionChange(item) onDismissRequest.invoke() }) - .clip(RoundedCornerShape(20.dp)) .padding(horizontal = 6.dp, vertical = 12.dp), verticalAlignment = Alignment.CenterVertically ) { diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/SettingItem.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/SettingItem.kt similarity index 96% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/components/SettingItem.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/SettingItem.kt index b724130d..a57a93ca 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/SettingItem.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/SettingItem.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.ui.components +package app.suhasdissa.vibeyou.presentation.screens.settings.components import android.view.SoundEffectConstants import androidx.compose.foundation.clickable diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/settings/SwitchPref.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/SwitchPref.kt similarity index 96% rename from app/src/main/java/app/suhasdissa/vibeyou/ui/screens/settings/SwitchPref.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/SwitchPref.kt index 6ee74e4b..559acb33 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/settings/SwitchPref.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/SwitchPref.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.ui.screens.settings +package app.suhasdissa.vibeyou.presentation.screens.settings.components import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource diff --git a/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/TextFieldPref.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/TextFieldPref.kt new file mode 100644 index 00000000..cb4e2f08 --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/components/TextFieldPref.kt @@ -0,0 +1,42 @@ +package app.suhasdissa.vibeyou.presentation.screens.settings.components + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +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.Modifier +import androidx.compose.ui.unit.dp +import androidx.core.content.edit +import app.suhasdissa.vibeyou.utils.Pref + +@Composable +fun TextFieldPref( + key: String, + defaultValue: String, + title: String, + onValueChange: (String) -> Unit = {} +) { + var value by remember { + mutableStateOf(Pref.sharedPreferences.getString(key, defaultValue).orEmpty()) + } + + OutlinedTextField( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp), + value = value, + onValueChange = { + value = it + Pref.sharedPreferences.edit(true) { putString(key, it) } + onValueChange(it) + }, + label = { + Text(text = title) + } + ) +} \ No newline at end of file diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/AuthViewModel.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/model/AuthViewModel.kt similarity index 100% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/AuthViewModel.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/model/AuthViewModel.kt diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/CheckUpdateViewModel.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/model/CheckUpdateViewModel.kt similarity index 90% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/CheckUpdateViewModel.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/model/CheckUpdateViewModel.kt index 88ee20f9..92596885 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/CheckUpdateViewModel.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/model/CheckUpdateViewModel.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.backend.viewmodel +package app.suhasdissa.vibeyou.presentation.screens.settings.model import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/DatabaseViewModel.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/model/DatabaseViewModel.kt similarity index 92% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/DatabaseViewModel.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/model/DatabaseViewModel.kt index 8ac6d445..8199d367 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/DatabaseViewModel.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/model/DatabaseViewModel.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.backend.viewmodel +package app.suhasdissa.vibeyou.presentation.screens.settings.model import android.content.Context import android.content.Intent @@ -10,12 +10,12 @@ import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory import androidx.sqlite.db.SimpleSQLiteQuery import app.suhasdissa.vibeyou.MellowMusicApplication -import app.suhasdissa.vibeyou.backend.database.SongDatabase -import app.suhasdissa.vibeyou.backend.services.PlayerService +import app.suhasdissa.vibeyou.data.database.SongDatabase +import app.suhasdissa.vibeyou.utils.services.PlayerService +import kotlinx.coroutines.launch import java.io.FileInputStream import java.io.FileOutputStream import kotlin.system.exitProcess -import kotlinx.coroutines.launch class DatabaseViewModel(private val database: SongDatabase) : ViewModel() { diff --git a/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/model/SettingsModel.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/model/SettingsModel.kt new file mode 100644 index 00000000..82b6d64b --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/model/SettingsModel.kt @@ -0,0 +1,66 @@ +package app.suhasdissa.vibeyou.presentation.screens.settings.model + +import android.content.Context +import android.content.SharedPreferences +import androidx.annotation.StringRes +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue +import androidx.core.content.edit +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory +import app.suhasdissa.vibeyou.R +import app.suhasdissa.vibeyou.utils.Pref +import app.suhasdissa.vibeyou.utils.catpucchinLatte +import app.suhasdissa.vibeyou.utils.mutableStatePreferenceOf +import app.suhasdissa.vibeyou.utils.preferences + +class SettingsModel(preferences: SharedPreferences) : ViewModel() { + enum class Theme(@StringRes val resId: Int) { + SYSTEM(R.string.system), LIGHT(R.string.light), DARK(R.string.dark), + AMOLED(R.string.amoled) + } + + enum class ColorTheme(@StringRes val resId: Int) { + SYSTEM(R.string.system), + CATPPUCCIN(R.string.catppuccin) + } + + private val themeModePref = + preferences.getString(Pref.themeKey, Theme.SYSTEM.name) ?: Theme.SYSTEM.name + + var themeMode: Theme by mutableStatePreferenceOf( + Theme.valueOf(themeModePref.uppercase()) + ) { + preferences.edit { putString(Pref.themeKey, it.name) } + } + + private val colorThemePref = + preferences.getString(Pref.colorThemeKey, ColorTheme.SYSTEM.name) + ?: ColorTheme.SYSTEM.name + + var colorTheme: ColorTheme by mutableStatePreferenceOf( + ColorTheme.valueOf(colorThemePref.uppercase()) + ) { + preferences.edit { putString(Pref.colorThemeKey, it.name) } + } + + var customColor by mutableStatePreferenceOf( + preferences.getInt( + Pref.customColorKey, + catpucchinLatte.first() + ) + ) { + preferences.edit { putInt(Pref.customColorKey, it) } + } + + companion object { + val Factory = viewModelFactory { + initializer { + val context = this[APPLICATION_KEY] as Context + SettingsModel(preferences = context.preferences) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/SettingsViewModel.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/model/SettingsViewModel.kt similarity index 83% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/SettingsViewModel.kt rename to app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/model/SettingsViewModel.kt index 17dbcfff..3196e6a7 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/SettingsViewModel.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/settings/model/SettingsViewModel.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.backend.viewmodel +package app.suhasdissa.vibeyou.presentation.screens.settings.model import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -27,7 +27,8 @@ class SettingsViewModel( companion object { val Factory: ViewModelProvider.Factory = viewModelFactory { initializer { - val application = (this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as MellowMusicApplication) + val application = + (this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as MellowMusicApplication) SettingsViewModel(application.container.pipedMusicRepository) } } diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/ArtistList.kt b/app/src/main/java/app/suhasdissa/vibeyou/ui/components/ArtistList.kt deleted file mode 100644 index 14b8b23a..00000000 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/ArtistList.kt +++ /dev/null @@ -1,48 +0,0 @@ -package app.suhasdissa.vibeyou.ui.components - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import app.suhasdissa.vibeyou.R -import app.suhasdissa.vibeyou.backend.data.Artist -import my.nanihadesuka.compose.LazyColumnScrollbar - -@Composable -fun ArtistList( - items: List, - onClickCard: (artist: Artist) -> Unit, - onLongPress: (artist: Artist) -> Unit -) { - if (items.isEmpty()) { - IllustratedMessageScreen(image = R.drawable.ic_launcher_monochrome) - } - val state = rememberLazyListState() - LazyColumnScrollbar( - listState = state, - thumbColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.5f), - thumbSelectedColor = MaterialTheme.colorScheme.primary, - thickness = 8.dp - ) { - LazyColumn( - modifier = Modifier.fillMaxSize(), - state = state, - verticalArrangement = Arrangement.spacedBy(8.dp), - contentPadding = PaddingValues(horizontal = 8.dp, vertical = 8.dp) - ) { - items(items = items) { item -> - ArtistCard( - artist = item, - onClickCard = { onClickCard(item) }, - onLongPress = { onLongPress(item) } - ) - } - } - } -} diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/MiniPlayerScaffold.kt b/app/src/main/java/app/suhasdissa/vibeyou/ui/components/MiniPlayerScaffold.kt deleted file mode 100644 index 0545db9c..00000000 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/components/MiniPlayerScaffold.kt +++ /dev/null @@ -1,86 +0,0 @@ -package app.suhasdissa.vibeyou.ui.components - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ModalBottomSheet -import androidx.compose.material3.Scaffold -import androidx.compose.material3.rememberModalBottomSheetState -import androidx.compose.runtime.Composable -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.Modifier -import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel -import app.suhasdissa.vibeyou.backend.viewmodel.PlayerViewModel -import app.suhasdissa.vibeyou.ui.screens.player.FullScreenPlayer -import app.suhasdissa.vibeyou.ui.screens.player.MiniPlayer -import app.suhasdissa.vibeyou.utils.mediaItemState -import kotlinx.coroutines.launch - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun MiniPlayerScaffold( - fab: @Composable () -> Unit = {}, - topBar: @Composable () -> Unit = {}, - content: @Composable () -> Unit -) { - val playerViewModel: PlayerViewModel = viewModel(factory = PlayerViewModel.Factory) - var isPlayerSheetVisible by remember { mutableStateOf(false) } - val playerSheetState = rememberModalBottomSheetState( - skipPartiallyExpanded = true - ) - val scope = rememberCoroutineScope() - if (isPlayerSheetVisible) { - ModalBottomSheet( - onDismissRequest = { isPlayerSheetVisible = false }, - sheetState = playerSheetState, - shape = RoundedCornerShape(8.dp), - dragHandle = null - ) { - playerViewModel.controller?.let { controller -> - FullScreenPlayer( - controller, - onCollapse = { - scope.launch { playerSheetState.hide() }.invokeOnCompletion { - if (!playerSheetState.isVisible) { - isPlayerSheetVisible = false - } - } - } - ) - } - } - } - Scaffold( - modifier = Modifier.fillMaxSize(), - floatingActionButton = fab, - topBar = topBar, - bottomBar = { - playerViewModel.controller?.let { controller -> - val mediaItem by controller.mediaItemState() - mediaItem?.let { - MiniPlayer( - onClick = { - isPlayerSheetVisible = true - scope.launch { - playerSheetState.show() - } - }, - controller, - it - ) - } - } - } - ) { innerPadding -> - Column(Modifier.padding(innerPadding)) { - content() - } - } -} diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/home/HomeScreen.kt b/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/home/HomeScreen.kt deleted file mode 100644 index 4e64526a..00000000 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/home/HomeScreen.kt +++ /dev/null @@ -1,174 +0,0 @@ -package app.suhasdissa.vibeyou.ui.screens.home - -import android.view.SoundEffectConstants -import androidx.compose.animation.EnterTransition -import androidx.compose.animation.ExitTransition -import androidx.compose.foundation.clickable -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.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.Menu -import androidx.compose.material.icons.rounded.Search -import androidx.compose.material3.Card -import androidx.compose.material3.CenterAlignedTopAppBar -import androidx.compose.material3.DrawerValue -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalNavigationDrawer -import androidx.compose.material3.Text -import androidx.compose.material3.rememberDrawerState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.LaunchedEffect -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.Alignment -import androidx.compose.ui.Modifier -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.unit.dp -import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner -import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController -import app.suhasdissa.vibeyou.Destination -import app.suhasdissa.vibeyou.MainActivity -import app.suhasdissa.vibeyou.R -import app.suhasdissa.vibeyou.backend.repository.LocalMusicRepository -import app.suhasdissa.vibeyou.backend.viewmodel.LocalSearchViewModel -import app.suhasdissa.vibeyou.navigateTo -import app.suhasdissa.vibeyou.ui.components.MiniPlayerScaffold -import app.suhasdissa.vibeyou.ui.components.NavDrawerContent -import app.suhasdissa.vibeyou.ui.screens.music.LocalMusicScreen -import app.suhasdissa.vibeyou.ui.screens.music.MusicScreen -import app.suhasdissa.vibeyou.utils.PermissionHelper -import kotlinx.coroutines.launch - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun HomeScreen( - onNavigate: (Destination) -> Unit, - localSearchViewModel: LocalSearchViewModel = viewModel(factory = LocalSearchViewModel.Factory) -) { - val navController = rememberNavController() - val drawerState = rememberDrawerState(DrawerValue.Closed) - val scope = rememberCoroutineScope() - val view = LocalView.current - val mainActivity = (LocalContext.current as MainActivity) - var currentDestination by remember { - mutableStateOf(Destination.LocalMusic) - } - ModalNavigationDrawer( - drawerState = drawerState, - gesturesEnabled = drawerState.isOpen, - drawerContent = { - NavDrawerContent(currentDestination = currentDestination, onDestinationSelected = { - scope.launch { - drawerState.close() - } - if (it == Destination.Settings) { - onNavigate(it) - } else { - navController.navigateTo(it.route) - currentDestination = it - } - }) - } - ) { - MiniPlayerScaffold(topBar = { - Row( - verticalAlignment = Alignment.CenterVertically - ) { - IconButton(onClick = { - view.playSoundEffect(SoundEffectConstants.CLICK) - scope.launch { - drawerState.open() - } - }) { - Icon( - imageVector = Icons.Rounded.Menu, - contentDescription = "Open Navigation Drawer" - ) - } - Card( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 12.dp) - .padding(end = 8.dp) - .clickable { - view.playSoundEffect(SoundEffectConstants.CLICK) - if (currentDestination == Destination.PipedMusic) { - onNavigate(Destination.OnlineSearch) - } else { - onNavigate(Destination.LocalSearch) - } - }, - shape = RoundedCornerShape(40) - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 2.dp) - .padding(end = 8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - modifier = Modifier.size(48.dp), - painter = painterResource(id = R.drawable.ic_launcher_foreground), - contentDescription = null - ) - Text(stringResource(R.string.search_songs)) - Spacer(Modifier.weight(1f)) - Icon( - modifier = Modifier.padding(8.dp), - imageVector = Icons.Rounded.Search, - contentDescription = null - ) - } - } - } - }) { - val viewModelStoreOwner = LocalViewModelStoreOwner.current!! - NavHost( - navController, - startDestination = Destination.LocalMusic.route, - Modifier - .fillMaxSize(), - enterTransition = { EnterTransition.None }, - exitTransition = { ExitTransition.None } - ) { - composable(Destination.PipedMusic.route) { - CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) { - MusicScreen(onNavigate) - LaunchedEffect(Unit) { - currentDestination = Destination.PipedMusic - } - } - } - composable(Destination.LocalMusic.route) { - LaunchedEffect(Unit) { - currentDestination = Destination.LocalMusic - PermissionHelper.checkPermissions( - mainActivity, - LocalMusicRepository.permissions - ) - } - LocalMusicScreen(onNavigate, localSearchViewModel) - } - } - } - } -} diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/music/MusicScreen.kt b/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/music/MusicScreen.kt deleted file mode 100644 index 011acd88..00000000 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/music/MusicScreen.kt +++ /dev/null @@ -1,91 +0,0 @@ -package app.suhasdissa.vibeyou.ui.screens.music - -import android.view.SoundEffectConstants -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.pager.HorizontalPager -import androidx.compose.foundation.pager.rememberPagerState -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Tab -import androidx.compose.material3.TabRow -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalView -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import app.suhasdissa.vibeyou.Destination -import app.suhasdissa.vibeyou.R -import app.suhasdissa.vibeyou.ui.screens.playlists.PlaylistsScreen -import app.suhasdissa.vibeyou.ui.screens.songs.SongsScreen -import kotlinx.coroutines.launch - -@OptIn(ExperimentalFoundationApi::class) -@Composable -fun MusicScreen( - onNavigate: (Destination) -> Unit -) { - val pagerState = rememberPagerState { 3 } - val scope = rememberCoroutineScope() - Column { - TabRow(selectedTabIndex = pagerState.currentPage, Modifier.fillMaxWidth()) { - val view = LocalView.current - Tab(selected = (pagerState.currentPage == 0), onClick = { - view.playSoundEffect(SoundEffectConstants.CLICK) - scope.launch { - pagerState.animateScrollToPage( - 0 - ) - } - }) { - Text( - stringResource(R.string.songs), - Modifier.padding(10.dp), - style = MaterialTheme.typography.titleMedium - ) - } - Tab(selected = (pagerState.currentPage == 1), onClick = { - view.playSoundEffect(SoundEffectConstants.CLICK) - scope.launch { - pagerState.animateScrollToPage( - 1 - ) - } - }) { - Text( - stringResource(R.string.favourite_songs), - Modifier.padding(10.dp), - style = MaterialTheme.typography.titleMedium - ) - } - Tab(selected = (pagerState.currentPage == 2), onClick = { - view.playSoundEffect(SoundEffectConstants.CLICK) - scope.launch { - pagerState.animateScrollToPage( - 2 - ) - } - }) { - Text( - stringResource(R.string.playlists), - Modifier.padding(10.dp), - style = MaterialTheme.typography.titleMedium - ) - } - } - HorizontalPager( - state = pagerState, - modifier = Modifier.fillMaxSize() - ) { index -> - when (index) { - 0 -> SongsScreen(showFavourites = false) - 1 -> SongsScreen(showFavourites = true) - 2 -> PlaylistsScreen(onNavigate = onNavigate) - } - } - } -} diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/search/AlbumScreen.kt b/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/search/AlbumScreen.kt deleted file mode 100644 index 5d7a51c3..00000000 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/search/AlbumScreen.kt +++ /dev/null @@ -1,194 +0,0 @@ -package app.suhasdissa.vibeyou.ui.screens.search - -import android.widget.Toast -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -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.LibraryAdd -import androidx.compose.material.icons.rounded.PlayArrow -import androidx.compose.material.icons.rounded.Shuffle -import androidx.compose.material3.Button -import androidx.compose.material3.Divider -import androidx.compose.material3.FilledTonalButton -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedButton -import androidx.compose.material3.Text -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.draw.clip -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel -import app.suhasdissa.vibeyou.R -import app.suhasdissa.vibeyou.backend.data.Song -import app.suhasdissa.vibeyou.backend.viewmodel.PlayerViewModel -import app.suhasdissa.vibeyou.backend.viewmodel.PlaylistViewModel -import app.suhasdissa.vibeyou.backend.viewmodel.state.AlbumInfoState -import app.suhasdissa.vibeyou.ui.components.IllustratedMessageScreen -import app.suhasdissa.vibeyou.ui.components.LoadingScreen -import app.suhasdissa.vibeyou.ui.components.MiniPlayerScaffold -import app.suhasdissa.vibeyou.ui.components.SongCard -import app.suhasdissa.vibeyou.ui.components.SongSettingsSheetSearchPage -import coil.compose.AsyncImage - -@Composable -fun AlbumScreen( - state: AlbumInfoState, - playerViewModel: PlayerViewModel = viewModel(factory = PlayerViewModel.Factory), - playlistViewModel: PlaylistViewModel = viewModel(factory = PlaylistViewModel.Factory) -) { - MiniPlayerScaffold { - when (state) { - AlbumInfoState.Error -> IllustratedMessageScreen( - image = R.drawable.ic_launcher_monochrome, - message = R.string.something_went_wrong - ) - - AlbumInfoState.Loading -> LoadingScreen() - is AlbumInfoState.Success -> { - var showSongSettings by remember { mutableStateOf(false) } - var selectedSong by remember { mutableStateOf(null) } - LazyColumn( - Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(8.dp), - contentPadding = PaddingValues(horizontal = 8.dp, vertical = 8.dp) - ) { - item { - Column( - Modifier - .fillMaxWidth() - .padding(8.dp) - .padding(bottom = 8.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - AsyncImage( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 40.dp, vertical = 20.dp) - .aspectRatio(1f) - .clip(RoundedCornerShape(16.dp)), - model = state.album.thumbnailUri, - contentDescription = stringResource(id = R.string.album_art), - contentScale = ContentScale.Crop, - error = painterResource(id = R.drawable.music_placeholder) - ) - Column( - Modifier - .fillMaxWidth() - .padding(8.dp) - ) { - Text( - text = state.album.title, - style = MaterialTheme.typography.titleLarge - ) - Text( - text = state.album.artistsText, - style = MaterialTheme.typography.bodyMedium - ) - state.album.numberOfSongs?.let { - Text( - text = "$it ${stringResource(id = R.string.songs)}", - style = MaterialTheme.typography.bodyMedium - ) - } - } - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - OutlinedButton(modifier = Modifier.weight(1f), onClick = { - playerViewModel.playSongs(state.songs, shuffle = false) - }) { - Icon( - imageVector = Icons.Rounded.PlayArrow, - contentDescription = null - ) - Text(text = stringResource(id = R.string.play_all)) - } - Button(modifier = Modifier.weight(1f), onClick = { - playerViewModel.playSongs(state.songs, shuffle = true) - }) { - Icon( - imageVector = Icons.Rounded.Shuffle, - contentDescription = null - ) - Text(text = stringResource(id = R.string.shuffle)) - } - } - if (!state.album.isLocal) { - val context = LocalContext.current - FilledTonalButton( - modifier = Modifier.fillMaxWidth(), - onClick = { - playlistViewModel.newPlaylistWithSongs( - state.album, - state.songs - ) - Toast.makeText( - context, - context.getString( - R.string.added_all_the_songs_to_the_library - ), - Toast.LENGTH_SHORT - ).show() - } - ) { - Icon( - imageVector = Icons.Rounded.LibraryAdd, - contentDescription = null - ) - Text(text = stringResource(R.string.add_to_library)) - } - } - Divider() - } - } - items(items = state.songs) { item -> - SongCard( - song = item, - onClickCard = { - playerViewModel.playSong(item) - if (!item.isLocal) { - playerViewModel.saveSong(item) - } - }, - onLongPress = { - selectedSong = item - showSongSettings = true - } - ) - } - } - - if (showSongSettings) { - selectedSong?.let { - SongSettingsSheetSearchPage( - onDismissRequest = { showSongSettings = false }, - song = it - ) - } - } - } - } - } -} diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/search/ArtistScreen.kt b/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/search/ArtistScreen.kt deleted file mode 100644 index 0fd60be7..00000000 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/search/ArtistScreen.kt +++ /dev/null @@ -1,52 +0,0 @@ -package app.suhasdissa.vibeyou.ui.screens.search - -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextOverflow -import app.suhasdissa.vibeyou.R -import app.suhasdissa.vibeyou.backend.data.Album -import app.suhasdissa.vibeyou.backend.viewmodel.state.ArtistInfoState -import app.suhasdissa.vibeyou.ui.components.AlbumList -import app.suhasdissa.vibeyou.ui.components.IllustratedMessageScreen -import app.suhasdissa.vibeyou.ui.components.LoadingScreen -import app.suhasdissa.vibeyou.ui.components.MiniPlayerScaffold - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun ArtistScreen( - onClickAlbum: (Album) -> Unit, - state: ArtistInfoState -) { - MiniPlayerScaffold(topBar = { - TopAppBar(title = { - when (state) { - is ArtistInfoState.Success -> Text( - text = state.artist.artistsText, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - - else -> Text(stringResource(R.string.artist)) - } - }, scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()) - }) { - when (state) { - ArtistInfoState.Error -> IllustratedMessageScreen( - image = R.drawable.ic_launcher_monochrome, - message = R.string.something_went_wrong - ) - - ArtistInfoState.Loading -> LoadingScreen() - is ArtistInfoState.Success -> { - AlbumList(items = state.playlists, onClickCard = { - onClickAlbum.invoke(it) - }, onLongPress = { - }) - } - } - } -} diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/search/LocalSearchScreen.kt b/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/search/LocalSearchScreen.kt deleted file mode 100644 index c4e49d8a..00000000 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/search/LocalSearchScreen.kt +++ /dev/null @@ -1,267 +0,0 @@ -package app.suhasdissa.vibeyou.ui.screens.search - -import android.view.SoundEffectConstants -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.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.ArrowBack -import androidx.compose.material.icons.rounded.Clear -import androidx.compose.material.icons.rounded.Close -import androidx.compose.material.icons.rounded.History -import androidx.compose.material.icons.rounded.Search -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.ListItem -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.SearchBar -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -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.platform.LocalView -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel -import app.suhasdissa.vibeyou.Destination -import app.suhasdissa.vibeyou.R -import app.suhasdissa.vibeyou.backend.data.Song -import app.suhasdissa.vibeyou.backend.viewmodel.LocalSearchViewModel -import app.suhasdissa.vibeyou.backend.viewmodel.PlayerViewModel -import app.suhasdissa.vibeyou.backend.viewmodel.state.SearchState -import app.suhasdissa.vibeyou.ui.components.AlbumList -import app.suhasdissa.vibeyou.ui.components.ArtistList -import app.suhasdissa.vibeyou.ui.components.ChipSelector -import app.suhasdissa.vibeyou.ui.components.IllustratedMessageScreen -import app.suhasdissa.vibeyou.ui.components.LoadingScreen -import app.suhasdissa.vibeyou.ui.components.MiniPlayerScaffold -import app.suhasdissa.vibeyou.ui.components.SongCard -import app.suhasdissa.vibeyou.ui.components.SongList -import app.suhasdissa.vibeyou.ui.components.SongSettingsSheetSearchPage - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun LocalSearchScreen( - onNavigate: (Destination) -> Unit, - localSearchViewModel: LocalSearchViewModel = viewModel(factory = LocalSearchViewModel.Factory), - playerViewModel: PlayerViewModel = viewModel(factory = PlayerViewModel.Factory) -) { - var isPopupOpen by remember { - mutableStateOf(localSearchViewModel.state !is SearchState.Success) - } - var showSongSettings by remember { mutableStateOf(false) } - var selectedSong by remember { mutableStateOf(null) } - val view = LocalView.current - MiniPlayerScaffold { - Column(Modifier.fillMaxSize()) { - Row(modifier = Modifier.fillMaxWidth()) { - SearchBar( - modifier = Modifier - .weight(1f), - query = localSearchViewModel.search, - onQueryChange = { - localSearchViewModel.search = it - if (it.length > 3) localSearchViewModel.getSuggestions() - }, - onSearch = { - localSearchViewModel.searchPiped() - isPopupOpen = false - }, - placeholder = { - Text( - stringResource(id = R.string.search_songs) - ) - }, - active = isPopupOpen, - onActiveChange = { - isPopupOpen = it - }, - leadingIcon = { - if (isPopupOpen) { - IconButton(onClick = { isPopupOpen = false }) { - Icon( - imageVector = Icons.Rounded.ArrowBack, - contentDescription = stringResource(R.string.close_search) - ) - } - } else { - Icon( - modifier = Modifier.size(48.dp), - painter = painterResource(id = R.drawable.ic_launcher_foreground), - contentDescription = null - ) - } - }, - trailingIcon = { - if (isPopupOpen) { - IconButton(onClick = { - view.playSoundEffect(SoundEffectConstants.CLICK) - localSearchViewModel.search = "" - }) { - Icon( - Icons.Rounded.Clear, - contentDescription = stringResource(R.string.clear_search) - ) - } - } else { - Icon( - modifier = Modifier.padding(8.dp), - imageVector = Icons.Rounded.Search, - contentDescription = null - ) - } - } - ) { - LaunchedEffect(Unit) { - localSearchViewModel.setSearchHistory() - } - val scroll = rememberScrollState() - Column( - Modifier - .verticalScroll(scroll) - ) { - if (localSearchViewModel.songSearchSuggestion.isNotEmpty()) { - Text( - text = stringResource(R.string.songs), - style = MaterialTheme.typography.titleSmall - ) - localSearchViewModel.songSearchSuggestion.forEach { item -> - SongCard( - song = item, - onClickCard = { - playerViewModel.playSong(item) - }, - onLongPress = { - selectedSong = item - showSongSettings = true - } - ) - } - } - localSearchViewModel.history.forEach { - ListItem( - modifier = Modifier.clickable { - localSearchViewModel.search = it - localSearchViewModel.searchPiped() - isPopupOpen = false - }, - headlineContent = { Text(it) }, - leadingContent = { - Icon( - imageVector = Icons.Rounded.History, - contentDescription = null - ) - }, - trailingContent = { - IconButton( - modifier = Modifier.offset(x = 16.dp), - onClick = { - localSearchViewModel.deleteFromHistory(it) - } - ) { - Icon( - imageVector = Icons.Rounded.Close, - contentDescription = null - ) - } - } - ) - } - } - } - } - Column { - Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { - ChipSelector(onItemSelected = { - localSearchViewModel.searchFilter = it - localSearchViewModel.searchPiped() - }, defaultValue = localSearchViewModel.searchFilter) - } - when (val searchState = localSearchViewModel.state) { - is SearchState.Loading -> { - LoadingScreen() - } - - is SearchState.Error -> { - IllustratedMessageScreen( - image = R.drawable.ic_launcher_monochrome, - message = R.string.something_went_wrong - ) - } - - is SearchState.Success -> { - when (searchState) { - is SearchState.Success.Playlists -> { - AlbumList( - items = searchState.items, - onClickCard = { - localSearchViewModel.getAlbumInfo(it) - onNavigate(Destination.LocalPlaylists) - }, - onLongPress = { - } - ) - } - - is SearchState.Success.Songs -> { - SongList( - items = searchState.items, - onClickCard = { song -> - playerViewModel.playSong(song) - }, - onLongPress = { song -> - selectedSong = song - showSongSettings = true - } - ) - } - - is SearchState.Success.Artists -> { - ArtistList( - items = searchState.items, - onClickCard = { - localSearchViewModel.getArtistInfo(it) - onNavigate(Destination.LocalArtist) - }, - onLongPress = { - } - ) - } - } - } - - is SearchState.Empty -> { - IllustratedMessageScreen( - image = R.drawable.ic_launcher_monochrome, - message = R.string.search_for_a_song, - messageColor = MaterialTheme.colorScheme.tertiary - ) - } - } - } - } - } - if (showSongSettings) { - selectedSong?.let { - SongSettingsSheetSearchPage( - onDismissRequest = { showSongSettings = false }, - song = selectedSong!! - ) - } - } -} diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/search/SearchScreen.kt b/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/search/SearchScreen.kt deleted file mode 100644 index daccd73f..00000000 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/search/SearchScreen.kt +++ /dev/null @@ -1,289 +0,0 @@ -package app.suhasdissa.vibeyou.ui.screens.search - -import android.view.SoundEffectConstants -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.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.ArrowBack -import androidx.compose.material.icons.rounded.Clear -import androidx.compose.material.icons.rounded.History -import androidx.compose.material.icons.rounded.Search -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.ListItem -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.SearchBar -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalView -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel -import app.suhasdissa.vibeyou.Destination -import app.suhasdissa.vibeyou.R -import app.suhasdissa.vibeyou.backend.data.Song -import app.suhasdissa.vibeyou.backend.viewmodel.PipedSearchViewModel -import app.suhasdissa.vibeyou.backend.viewmodel.PlayerViewModel -import app.suhasdissa.vibeyou.backend.viewmodel.state.SearchState -import app.suhasdissa.vibeyou.ui.components.AlbumList -import app.suhasdissa.vibeyou.ui.components.ArtistList -import app.suhasdissa.vibeyou.ui.components.ChipSelector -import app.suhasdissa.vibeyou.ui.components.IllustratedMessageScreen -import app.suhasdissa.vibeyou.ui.components.LoadingScreen -import app.suhasdissa.vibeyou.ui.components.MiniPlayerScaffold -import app.suhasdissa.vibeyou.ui.components.SongCard -import app.suhasdissa.vibeyou.ui.components.SongList -import app.suhasdissa.vibeyou.ui.components.SongSettingsSheetSearchPage - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun SearchScreen( - onNavigate: (Destination) -> Unit, - pipedSearchViewModel: PipedSearchViewModel = viewModel(factory = PipedSearchViewModel.Factory), - playerViewModel: PlayerViewModel = viewModel(factory = PlayerViewModel.Factory) -) { - var isPopupOpen by remember { - mutableStateOf(pipedSearchViewModel.state !is SearchState.Success) - } - var showSongSettings by remember { mutableStateOf(false) } - var selectedSong by remember { mutableStateOf(null) } - val view = LocalView.current - MiniPlayerScaffold { - Column(Modifier.fillMaxSize()) { - Row(modifier = Modifier.fillMaxWidth()) { - SearchBar( - modifier = Modifier - .weight(1f), - query = pipedSearchViewModel.search, - onQueryChange = { - pipedSearchViewModel.search = it - if (it.length > 3) pipedSearchViewModel.getSuggestions() - }, - onSearch = { - pipedSearchViewModel.searchPiped() - isPopupOpen = false - }, - placeholder = { - Text( - stringResource(id = R.string.search_songs) - ) - }, - active = isPopupOpen, - onActiveChange = { - isPopupOpen = it - }, - leadingIcon = { - if (isPopupOpen) { - IconButton(onClick = { isPopupOpen = false }) { - Icon( - imageVector = Icons.Rounded.ArrowBack, - contentDescription = stringResource(R.string.close_search) - ) - } - } else { - Icon( - modifier = Modifier.size(48.dp), - painter = painterResource(id = R.drawable.ic_launcher_foreground), - contentDescription = null - ) - } - }, - trailingIcon = { - if (isPopupOpen) { - IconButton(onClick = { - view.playSoundEffect(SoundEffectConstants.CLICK) - pipedSearchViewModel.search = "" - }) { - Icon( - Icons.Rounded.Clear, - contentDescription = stringResource(R.string.clear_search) - ) - } - } else { - Icon( - modifier = Modifier.padding(8.dp), - imageVector = Icons.Rounded.Search, - contentDescription = null - ) - } - } - ) { - LaunchedEffect(Unit) { - pipedSearchViewModel.setSearchHistory() - } - val scroll = rememberScrollState() - Column( - Modifier - .verticalScroll(scroll) - .padding(horizontal = 8.dp) - ) { - if (pipedSearchViewModel.suggestions.isNotEmpty()) { - pipedSearchViewModel.suggestions.forEach { - ListItem( - modifier = Modifier.clickable { - pipedSearchViewModel.search = it - pipedSearchViewModel.searchPiped() - isPopupOpen = false - }, - headlineContent = { Text(it) }, - leadingContent = { - Icon( - imageVector = Icons.Rounded.Search, - contentDescription = null - ) - } - ) - } - } else { - pipedSearchViewModel.history.forEach { - ListItem( - modifier = Modifier.clickable { - pipedSearchViewModel.search = it - pipedSearchViewModel.searchPiped() - isPopupOpen = false - }, - headlineContent = { Text(it) }, - leadingContent = { - Icon( - imageVector = Icons.Rounded.History, - contentDescription = null - ) - } - ) - } - } - if (pipedSearchViewModel.songSearchSuggestion.isNotEmpty()) { - Text( - text = stringResource(R.string.recent_songs), - style = MaterialTheme.typography.titleSmall - ) - pipedSearchViewModel.songSearchSuggestion.forEach { item -> - SongCard( - song = item, - onClickCard = { - playerViewModel.playSong(item) - }, - onLongPress = { - selectedSong = item - showSongSettings = true - } - ) - } - } - } - } - } - Column { - Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { - ChipSelector(onItemSelected = { - pipedSearchViewModel.searchFilter = it - pipedSearchViewModel.searchPiped() - }, defaultValue = pipedSearchViewModel.searchFilter) - } - when (val searchState = pipedSearchViewModel.state) { - is SearchState.Loading -> { - LoadingScreen() - } - - is SearchState.Error -> { - IllustratedMessageScreen( - image = R.drawable.ic_launcher_monochrome, - message = R.string.something_went_wrong, - action = { - TextButton( - onClick = { - onNavigate(Destination.NetworkSettings) - }, - colors = ButtonDefaults.textButtonColors( - containerColor = MaterialTheme.colorScheme.errorContainer, - contentColor = MaterialTheme.colorScheme.onErrorContainer - ) - ) { - Text(stringResource(id = R.string.network_settings)) - } - } - ) - } - - is SearchState.Success -> { - when (searchState) { - is SearchState.Success.Playlists -> { - AlbumList( - items = searchState.items, - onClickCard = { - pipedSearchViewModel.getPlaylistInfo(it) - onNavigate(Destination.Playlists) - }, - onLongPress = { - } - ) - } - - is SearchState.Success.Songs -> { - SongList( - items = searchState.items, - onClickCard = { song -> - playerViewModel.playSong(song) - if (!song.isLocal) { - playerViewModel.saveSong(song) - } - }, - onLongPress = { song -> - selectedSong = song - showSongSettings = true - } - ) - } - - is SearchState.Success.Artists -> { - ArtistList( - items = searchState.items, - onClickCard = { - pipedSearchViewModel.getChannelInfo(it) - onNavigate(Destination.Artist) - }, - onLongPress = { - } - ) - } - } - } - - is SearchState.Empty -> { - IllustratedMessageScreen( - image = R.drawable.ic_launcher_monochrome, - message = R.string.search_for_a_song, - messageColor = MaterialTheme.colorScheme.tertiary - ) - } - } - } - } - } - if (showSongSettings) { - selectedSong?.let { - SongSettingsSheetSearchPage( - onDismissRequest = { showSongSettings = false }, - song = selectedSong!! - ) - } - } -} diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/settings/AppearanceSettingsScreen.kt b/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/settings/AppearanceSettingsScreen.kt deleted file mode 100644 index 1010b3aa..00000000 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/settings/AppearanceSettingsScreen.kt +++ /dev/null @@ -1,44 +0,0 @@ -package app.suhasdissa.vibeyou.ui.screens.settings - -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.LargeTopAppBar -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.res.stringResource -import app.suhasdissa.vibeyou.R -import app.suhasdissa.vibeyou.utils.Pref - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun AppearanceSettingsScreen() { - val topBarBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() - - Scaffold(modifier = Modifier.fillMaxSize(), topBar = { - LargeTopAppBar( - title = { Text(stringResource(R.string.appearance_settings)) }, - scrollBehavior = topBarBehavior - ) - }) { innerPadding -> - LazyColumn( - Modifier - .fillMaxSize() - .padding(innerPadding) - .nestedScroll(topBarBehavior.nestedScrollConnection) - ) { - item { - SwitchPref( - prefKey = Pref.thumbnailColorFallbackKey, - title = stringResource(R.string.fallback_thumnail_accent), - summary = stringResource(R.string.fallback_thumnail_accent_description) - ) - } - } - } -} diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/theme/Theme.kt b/app/src/main/java/app/suhasdissa/vibeyou/ui/theme/Theme.kt index 47f12420..79ac3d9b 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/theme/Theme.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/ui/theme/Theme.kt @@ -3,63 +3,55 @@ package app.suhasdissa.vibeyou.ui.theme import android.app.Activity import android.os.Build import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.ColorScheme import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.darkColorScheme import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicLightColorScheme -import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect -import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.core.view.WindowCompat -private val DarkColorScheme = darkColorScheme( - primary = Purple80, - secondary = PurpleGrey80, - tertiary = Pink80 -) - -private val LightColorScheme = lightColorScheme( - primary = Purple40, - secondary = PurpleGrey40, - tertiary = Pink40 - - /* Other default colors to override - background = Color(0xFFFFFBFE), - surface = Color(0xFFFFFBFE), - onPrimary = Color.White, - onSecondary = Color.White, - onTertiary = Color.White, - onBackground = Color(0xFF1C1B1F), - onSurface = Color(0xFF1C1B1F), - */ -) - @Composable -fun LibreMusicTheme( +fun VibeYouTheme( darkTheme: Boolean = isSystemInDarkTheme(), - // Dynamic color is available on Android 12+ + customColorScheme: ColorScheme, dynamicColor: Boolean = true, + amoledDark: Boolean = false, content: @Composable () -> Unit ) { val colorScheme = when { + amoledDark -> { + if (dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val context = LocalContext.current + dynamicDarkColorScheme(context).copy( + background = Color.Black, + surface = Color.Black + ) + } else { + customColorScheme.copy(background = Color.Black, surface = Color.Black) + } + } + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { val context = LocalContext.current if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) } - darkTheme -> DarkColorScheme - else -> LightColorScheme + else -> customColorScheme } val view = LocalView.current if (!view.isInEditMode) { SideEffect { - val window = (view.context as Activity).window - window.statusBarColor = colorScheme.background.toArgb() - window.navigationBarColor = colorScheme.background.toArgb() - WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme + val activity = view.context as Activity + val insetsController = WindowCompat.getInsetsController( + activity.window, + view + ) + insetsController.isAppearanceLightStatusBars = !darkTheme + insetsController.isAppearanceLightNavigationBars = !darkTheme } } diff --git a/app/src/main/java/app/suhasdissa/vibeyou/utils/Mappers.kt b/app/src/main/java/app/suhasdissa/vibeyou/utils/Mappers.kt index a1e96338..58210322 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/utils/Mappers.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/utils/Mappers.kt @@ -6,14 +6,14 @@ import androidx.core.net.toUri import androidx.core.os.bundleOf import androidx.media3.common.MediaItem import androidx.media3.common.MediaMetadata -import app.suhasdissa.vibeyou.backend.data.Album -import app.suhasdissa.vibeyou.backend.data.Artist -import app.suhasdissa.vibeyou.backend.data.Song -import app.suhasdissa.vibeyou.backend.database.entities.PlaylistEntity -import app.suhasdissa.vibeyou.backend.database.entities.SongEntity import app.suhasdissa.vibeyou.backend.models.PipedSongResponse import app.suhasdissa.vibeyou.backend.models.playlists.Playlist import app.suhasdissa.vibeyou.backend.models.songs.SongItem +import app.suhasdissa.vibeyou.data.database.entities.PlaylistEntity +import app.suhasdissa.vibeyou.data.database.entities.SongEntity +import app.suhasdissa.vibeyou.domain.models.primary.Album +import app.suhasdissa.vibeyou.domain.models.primary.Artist +import app.suhasdissa.vibeyou.domain.models.primary.Song val SongItem.asSong: Song get() = Song( @@ -100,6 +100,7 @@ val Song.asMediaItem: MediaItem ) .build() ) + .setUri(id) .setMediaId(id) .setCustomCacheKey(id) .build() diff --git a/app/src/main/java/app/suhasdissa/vibeyou/utils/Player.kt b/app/src/main/java/app/suhasdissa/vibeyou/utils/Player.kt index a19d0bfc..63eb7833 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/utils/Player.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/utils/Player.kt @@ -24,6 +24,9 @@ fun Player.playPause() { if (isPlaying) { pause() } else { + if (playbackState == Player.STATE_IDLE) { + prepare() + } play() } } @@ -37,10 +40,26 @@ fun Player.forcePlay(mediaItem: MediaItem) { fun Player.playGracefully(mediaItem: MediaItem) { if (playbackState == Player.STATE_IDLE || playbackState == Player.STATE_ENDED) { forcePlay(mediaItem) + } else { + val newIndex = currentPeriodIndex + 1 + addMediaItem(newIndex, mediaItem) + seekTo(newIndex, C.TIME_UNSET) + } +} + +fun Player.seekNext() { + if (playbackState == Player.STATE_IDLE) { + prepare() + } + seekToNext() + +} + +fun Player.seek(position: Long) { + if (playbackState == Player.STATE_IDLE) { + prepare() } - val newIndex = currentPeriodIndex + 1 - addMediaItem(newIndex, mediaItem) - seekTo(newIndex, C.TIME_UNSET) + seekTo(position) } fun Player.enqueue(mediaItem: MediaItem) { diff --git a/app/src/main/java/app/suhasdissa/vibeyou/utils/Pref.kt b/app/src/main/java/app/suhasdissa/vibeyou/utils/Pref.kt index aa446aef..025513ab 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/utils/Pref.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/utils/Pref.kt @@ -5,6 +5,7 @@ import androidx.core.content.edit import app.suhasdissa.vibeyou.backend.models.PipedInstance import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull object Pref { private const val pipedInstanceKey = "SelectedPipedInstanceKey" @@ -15,47 +16,68 @@ object Pref { const val latestReverseSongsPrefKey = "LatestReverseSongsPrefKey" const val customPipedInstanceKey = "CustomPipedInstanceKey" const val disableSearchHistoryKey = "DisableSearchHistory" + const val hyperpipeApiUrlKey = "HyperpipeApiUrl" + const val customColorKey = "customColor" + const val themeKey = "theme" + const val colorThemeKey = "colorTheme" + const val equalizerKey = "equalizer" + const val equalizerPresetKey = "equalizerPreset" + const val equalizerBandsKey = "equalizerBands" lateinit var sharedPreferences: SharedPreferences + val defaultHyperInstance = "https://hyperpipeapi.onrender.com" + val pipedInstances = listOf( + PipedInstance( + "kavin.rocks Libre", + "https://pipedapi-libre.kavin.rocks" + ), PipedInstance( "kavin.rocks", - "https://pipedapi.kavin.rocks/" + "https://pipedapi.kavin.rocks" ), PipedInstance( "lunar.icu", - "https://piped-api.lunar.icu/" + "https://piped-api.lunar.icu" ), PipedInstance( "whatever.social", - "https://watchapi.whatever.social/" + "https://watchapi.whatever.social" ), PipedInstance( "tokhmi.xyz", - "https://pipedapi.tokhmi.xyz/" + "https://pipedapi.tokhmi.xyz" ), PipedInstance( "mha.fi", - "https://api-piped.mha.fi/" + "https://api-piped.mha.fi" ), PipedInstance( "garudalinux.org", - "https://piped-api.garudalinux.org/" + "https://piped-api.garudalinux.org" ), PipedInstance( "piped.yt", - "https://api.piped.yt/" + "https://api.piped.yt" ) ) - val currentInstance get() = run { - runCatching { - val instanceJsonStr = sharedPreferences.getString(pipedInstanceKey, "").orEmpty() - return@run json.decodeFromString(instanceJsonStr) + val currentInstance + get() = run { + runCatching { + val instanceJsonStr = sharedPreferences.getString(pipedInstanceKey, "").orEmpty() + return@run json.decodeFromString(instanceJsonStr) + } + pipedInstances.first() + } + + val hyperInstance: String? + get() { + val url = sharedPreferences.getString(hyperpipeApiUrlKey, defaultHyperInstance) + ?: defaultHyperInstance + return url.toHttpUrlOrNull()?.host } - pipedInstances.first() - } private val json = Json { ignoreUnknownKeys = true } diff --git a/app/src/main/java/app/suhasdissa/vibeyou/utils/RetrofitHelper.kt b/app/src/main/java/app/suhasdissa/vibeyou/utils/RetrofitHelper.kt index 1d94aa9d..48f4f154 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/utils/RetrofitHelper.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/utils/RetrofitHelper.kt @@ -1,20 +1,33 @@ package app.suhasdissa.vibeyou.utils -import app.suhasdissa.vibeyou.backend.api.PipedApi +import app.suhasdissa.vibeyou.data.api.HyperpipeApi +import app.suhasdissa.vibeyou.data.api.PipedApi import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import kotlinx.serialization.json.Json import okhttp3.MediaType.Companion.toMediaType import retrofit2.Retrofit +import retrofit2.create object RetrofitHelper { - fun createPipedApi(): PipedApi { - val json = Json { ignoreUnknownKeys = true } + val json by lazy { + Json { ignoreUnknownKeys = true } + } + fun createPipedApi(): PipedApi { val retrofit: Retrofit = Retrofit.Builder() .baseUrl("https://pipedapi.kavin.rocks/") .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) .build() - return retrofit.create(PipedApi::class.java) + return retrofit.create() + } + + fun createHyperpipeApi(): HyperpipeApi { + val retrofit = Retrofit.Builder() + .baseUrl("https://hyperpipeapi.onrender.com") + .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) + .build() + + return retrofit.create() } } diff --git a/app/src/main/java/app/suhasdissa/vibeyou/utils/ThemeUtil.kt b/app/src/main/java/app/suhasdissa/vibeyou/utils/ThemeUtil.kt new file mode 100644 index 00000000..43d99130 --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/utils/ThemeUtil.kt @@ -0,0 +1,67 @@ +package app.suhasdissa.vibeyou.utils + +import android.annotation.SuppressLint +import androidx.compose.material3.ColorScheme +import androidx.compose.ui.graphics.Color +import com.google.android.material.color.utilities.Scheme + +object ThemeUtil { + @SuppressLint("RestrictedApi") + fun getSchemeFromSeed(color: Int, dark: Boolean): ColorScheme { + return if (dark) { + Scheme.dark(color).toColorScheme() + } else { + Scheme.light(color).toColorScheme() + } + } +} + +val catpucchinLatte = arrayOf( + android.graphics.Color.rgb(220, 138, 120), + android.graphics.Color.rgb(221, 120, 120), + android.graphics.Color.rgb(234, 118, 203), + android.graphics.Color.rgb(136, 57, 239), + android.graphics.Color.rgb(210, 15, 57), + android.graphics.Color.rgb(230, 69, 83), + android.graphics.Color.rgb(254, 100, 11), + android.graphics.Color.rgb(223, 142, 29), + android.graphics.Color.rgb(64, 160, 43), + android.graphics.Color.rgb(23, 146, 153), + android.graphics.Color.rgb(4, 165, 229), + android.graphics.Color.rgb(32, 159, 181), + android.graphics.Color.rgb(30, 102, 245), + android.graphics.Color.rgb(114, 135, 253) +) + +@SuppressLint("RestrictedApi") +fun Scheme.toColorScheme() = ColorScheme( + primary = Color(primary), + onPrimary = Color(onPrimary), + primaryContainer = Color(primaryContainer), + onPrimaryContainer = Color(onPrimaryContainer), + inversePrimary = Color(inversePrimary), + secondary = Color(secondary), + onSecondary = Color(onSecondary), + secondaryContainer = Color(secondaryContainer), + onSecondaryContainer = Color(onSecondaryContainer), + tertiary = Color(tertiary), + onTertiary = Color(onTertiary), + tertiaryContainer = Color(tertiaryContainer), + onTertiaryContainer = Color(onTertiaryContainer), + background = Color(background), + onBackground = Color(onBackground), + surface = Color(surface), + onSurface = Color(onSurface), + surfaceVariant = Color(surfaceVariant), + onSurfaceVariant = Color(onSurfaceVariant), + surfaceTint = Color(primary), + inverseSurface = Color(inverseSurface), + inverseOnSurface = Color(inverseOnSurface), + error = Color(error), + onError = Color(onError), + errorContainer = Color(errorContainer), + onErrorContainer = Color(onErrorContainer), + outline = Color(outline), + outlineVariant = Color(outlineVariant), + scrim = Color(scrim) +) diff --git a/app/src/main/java/app/suhasdissa/vibeyou/utils/UpdateUtil.kt b/app/src/main/java/app/suhasdissa/vibeyou/utils/UpdateUtil.kt index 2378fe6f..cfa59e1f 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/utils/UpdateUtil.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/utils/UpdateUtil.kt @@ -4,7 +4,6 @@ import android.content.Context import android.content.pm.PackageManager import android.os.Build import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory -import java.util.regex.Pattern import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json @@ -12,6 +11,7 @@ import okhttp3.MediaType.Companion.toMediaType import retrofit2.Retrofit import retrofit2.http.GET import retrofit2.http.Headers +import java.util.regex.Pattern object UpdateUtil { var currentVersion = 0f diff --git a/app/src/main/java/app/suhasdissa/vibeyou/utils/UriSerializer.kt b/app/src/main/java/app/suhasdissa/vibeyou/utils/UriSerializer.kt new file mode 100644 index 00000000..67edae15 --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/utils/UriSerializer.kt @@ -0,0 +1,24 @@ +package app.suhasdissa.vibeyou.utils + +import android.net.Uri +import androidx.core.net.toUri +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +class UriSerializer : KSerializer { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("Uri", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): Uri { + return decoder.decodeString().toUri() + } + + override fun serialize(encoder: Encoder, value: Uri) { + encoder.encodeString(value.toString()) + } +} \ No newline at end of file diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/services/PlayerService.kt b/app/src/main/java/app/suhasdissa/vibeyou/utils/services/PlayerService.kt similarity index 61% rename from app/src/main/java/app/suhasdissa/vibeyou/backend/services/PlayerService.kt rename to app/src/main/java/app/suhasdissa/vibeyou/utils/services/PlayerService.kt index 82eec39d..15482d29 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/services/PlayerService.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/utils/services/PlayerService.kt @@ -1,4 +1,4 @@ -package app.suhasdissa.vibeyou.backend.services +package app.suhasdissa.vibeyou.utils.services import android.annotation.SuppressLint import android.content.Context @@ -7,15 +7,20 @@ import android.graphics.BitmapFactory import android.graphics.Canvas import android.graphics.Paint import android.graphics.Rect +import android.media.audiofx.Equalizer import android.media.audiofx.LoudnessEnhancer import android.net.Uri +import android.os.Bundle import android.os.Handler +import android.util.Log import androidx.annotation.ColorInt import androidx.core.graphics.drawable.toBitmap import androidx.media3.common.AudioAttributes import androidx.media3.common.C import androidx.media3.common.MediaItem +import androidx.media3.common.PlaybackException import androidx.media3.common.Player +import androidx.media3.common.util.UnstableApi import androidx.media3.database.StandaloneDatabaseProvider import androidx.media3.datasource.DataSource import androidx.media3.datasource.DefaultDataSource @@ -38,9 +43,16 @@ import androidx.media3.exoplayer.source.MediaSource import androidx.media3.session.BitmapLoader import androidx.media3.session.MediaSession import androidx.media3.session.MediaSessionService +import androidx.media3.session.SessionCommand +import androidx.media3.session.SessionResult import app.suhasdissa.vibeyou.MellowMusicApplication +import app.suhasdissa.vibeyou.domain.models.primary.EqualizerBand +import app.suhasdissa.vibeyou.domain.models.primary.EqualizerData +import app.suhasdissa.vibeyou.domain.models.primary.Song import app.suhasdissa.vibeyou.utils.DynamicDataSource +import app.suhasdissa.vibeyou.utils.IS_LOCAL_KEY import app.suhasdissa.vibeyou.utils.Pref +import app.suhasdissa.vibeyou.utils.asMediaItem import coil.ImageLoader import coil.request.ErrorResult import coil.request.ImageRequest @@ -52,17 +64,51 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import java.net.ConnectException +import java.net.SocketTimeoutException +import java.net.UnknownHostException +@UnstableApi class PlayerService : MediaSessionService(), MediaSession.Callback, Player.Listener { private var mediaSession: MediaSession? = null private lateinit var cache: SimpleCache private lateinit var player: ExoPlayer + private lateinit var equalizer: Equalizer private var loudnessEnhancer: LoudnessEnhancer? = null val appInstance get() = application as MellowMusicApplication val container get() = appInstance.container + private val mediaSessionCallback = object : MediaSession.Callback { + override fun onConnect( + session: MediaSession, + controller: MediaSession.ControllerInfo + ): MediaSession.ConnectionResult { + val sessionCommands = MediaSession.ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon() + .add(SessionCommand(COMMAND_UPDATE_EQUALIZER, Bundle.EMPTY)) + .build() + + return MediaSession.ConnectionResult.AcceptedResultBuilder(session) + .setAvailableSessionCommands(sessionCommands) + .build() + } + + override fun onCustomCommand( + session: MediaSession, + controller: MediaSession.ControllerInfo, + customCommand: SessionCommand, + args: Bundle + ): ListenableFuture { + if (customCommand.customAction == COMMAND_UPDATE_EQUALIZER) { + updateEqualizerSettings() + } + + return super.onCustomCommand(session, controller, customCommand, args) + } + } + @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) override fun onCreate() { super.onCreate() @@ -81,6 +127,7 @@ class PlayerService : MediaSessionService(), MediaSession.Callback, Player.Liste cache = SimpleCache(directory, cacheEvictor, StandaloneDatabaseProvider(this)) player = createPlayer() + setupEqualizer() player.repeatMode = Player.REPEAT_MODE_OFF player.playWhenReady = true @@ -88,6 +135,7 @@ class PlayerService : MediaSessionService(), MediaSession.Callback, Player.Liste mediaSession = MediaSession.Builder(this, player).setCallback(this) .setBitmapLoader(CustomBitmapLoader(this)) + .setCallback(mediaSessionCallback) .build() } @@ -206,26 +254,82 @@ class PlayerService : MediaSessionService(), MediaSession.Callback, Player.Liste @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) private fun createDataSourceFactory(): DataSource.Factory { - val chunkLength = 512 * 1024L - val defaultDataSource = DefaultDataSource.Factory(this@PlayerService) val resolvingDataSource = ResolvingDataSource.Factory(createCacheDataSource()) { dataSpec -> val videoId = dataSpec.key ?: error("A key must be set") - - if (cache.isCached(videoId, dataSpec.position, chunkLength)) { - dataSpec + val cacheLength = cache.getCachedBytes(videoId, dataSpec.position, Long.MAX_VALUE) + if (cacheLength > 0) { + dataSpec.subrange(dataSpec.position, dataSpec.position + cacheLength) } else { - val url = runBlocking { + val url = runBlocking(Dispatchers.IO) { container.pipedMusicRepository.getAudioSource(videoId) + }.getOrElse { throwable -> + when (throwable) { + is ConnectException, is UnknownHostException -> { + throw PlaybackException( + "Connection failed", + throwable, + PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED + ) + } + + is SocketTimeoutException -> { + throw PlaybackException( + "Timeout", + throwable, + PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT + ) + } + + else -> throw PlaybackException( + "Unknown error", + throwable, + PlaybackException.ERROR_CODE_REMOTE_ERROR + ) + } + } + if (url == null) { + throw PlaybackException( + "Media not found", + error("Media not found"), + PlaybackException.ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE + ) } - url?.let { - dataSpec.withUri(it).subrange(dataSpec.uriPositionOffset, chunkLength) - } ?: error("Stream not found") + dataSpec.withUri(url) } } return DynamicDataSource.Companion.Factory(resolvingDataSource, defaultDataSource) } + override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { + val isLocal = mediaItem?.mediaMetadata?.extras?.getBoolean(IS_LOCAL_KEY) ?: false + if (isLocal) return + val mediaId = mediaItem?.mediaId ?: return + CoroutineScope(Dispatchers.Main).launch { + appendToQueue(mediaId) + } + super.onMediaItemTransition(mediaItem, reason) + } + + private suspend fun appendToQueue(videoId: String) { + // enough other videos left in the queue + if (player.mediaItemCount - player.currentMediaItemIndex > 5) return + + val nextSongs = try { + withContext(Dispatchers.IO) { + container.pipedMusicRepository.getRecommendedSongs(videoId) + } + } catch (e: Exception) { + Log.e("hyperpipe: error fetching next", e.message, e) + emptyList() + } + + player.addMediaItems( + player.currentMediaItemIndex + 1, + nextSongs.map(Song::asMediaItem) + ) + } + @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) private fun createMediaSourceFactory(): MediaSource.Factory { return DefaultMediaSourceFactory(createDataSourceFactory()) @@ -270,4 +374,55 @@ class PlayerService : MediaSessionService(), MediaSession.Callback, Player.Liste ) } } + + private fun setupEqualizer() { + equalizer = Equalizer(Integer.MAX_VALUE, player.audioSessionId) + appInstance.supportedEqualizerData = EqualizerData( + presets = (0 until equalizer.numberOfPresets).map { + equalizer.getPresetName(it.toShort()) + }, + bands = (0 until equalizer.numberOfBands).map { id -> + val freqRange = equalizer.bandLevelRange + EqualizerBand( + equalizer.getCenterFreq(id.toShort()), + freqRange.first(), + freqRange.last() + ) + } + ) + + updateEqualizerSettings() + } + + /* private fun useSystemEqualizer() { + val intent = Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION); + intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, player.audioSessionId); + intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, packageName); + sendBroadcast(intent); + }*/ + + private fun updateEqualizerSettings() { + if (!::equalizer.isInitialized) return + + equalizer.enabled = Pref.sharedPreferences.getBoolean(Pref.equalizerKey, false) + if (!equalizer.enabled) return + + val preset = Pref.sharedPreferences.getInt(Pref.equalizerPresetKey, -1) + if (preset != -1) { + equalizer.usePreset(preset.toShort()) + return + } + + val bandPrefs = Pref.sharedPreferences.getString(Pref.equalizerBandsKey, "") + if (bandPrefs.isNullOrEmpty()) return + + val bandLevels = bandPrefs.split(",").map(String::toShort) + for (i in bandLevels.indices) { + equalizer.setBandLevel(i.toShort(), bandLevels[i]) + } + } + + companion object { + const val COMMAND_UPDATE_EQUALIZER = "update_equalizer" + } } diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index c82b65f7..9e8a8fb5 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -84,4 +84,17 @@ حذف قائمة التشغيل والأغاني مسح قائمة التشغيل تعطيل سجل البحث + عنوان URL لواجهة برمجة تطبيقات Hyperpipe + النظام + شديد السواد + نظام الألوان + فاتح + مظلم + كاتبوتشين + السمة + المعادل + الضبط المسبق للمعادل + مفعل + لاشيء + اعادة تعيين المعادل \ No newline at end of file diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml new file mode 100644 index 00000000..275adf0c --- /dev/null +++ b/app/src/main/res/values-ca/strings.xml @@ -0,0 +1,87 @@ + + + Configuració + Cançons + Versió, Contacte + Preferits + Posa a la cua + Reprodueix la cançó + Suprimeix la cançó + Configuració + Repetició desactivada + Repeteix tot + Repeteix-ne una + Canvia el servidor + Fes una còpia de seguretat de la llista de cançons o restaura una còpia. + Còpia de seguretat + Crea un fitxer de còpia de seguretat + Restaura un fitxer de còpia de seguretat + Restaura + Caràtula de l\'àlbum + Foto de l\'artista + Seleccioneu el servidor + Mostra la cua + Artista + Àlbums + Cançons recents + Tanca la cerca + Piped Music + Música local + Artistes + D\'acord + Canvia la memòria cau de la música + Límit de memòria cau de la música + Il·limitat + Ordre de classificació + Cancel·la + Invertit + Aspecte + Personalitza l\'aspecte per adaptar-la a les teves necessitats. + Notificació de miniatura alternativa + Utilitza el color d\'accent del sistema/tema com a miniatura alternativa. + Afegeix-ho a la biblioteca + S\'han afegit totes les cançons a la biblioteca + Afegeix-ho tot a la biblioteca + Neteja la cua + Quant a + README + Consulteu el repositori i el README + Últim llançament + Versió actual + Tiquet GitHub + Envieu informes d\'errors i peticions de funcions + Neteja la cerca + Cerca cançons + Cerca una cançó + Reprodueix la següent + S\'està reproduint + Opcions de la cançó + Tanca el reproductor + Caràtula de l\'àlbum + Pausa + Reprodueix + Salta la següent + Elimina l\'element de la cua + Tanca la cua + Cua del reproductor + Aleatori + Alguna cosa ha anat malament + Configuració de xarxa + Canvieu el servidor per solucionar problemes de reproducció + Còpia de seguretat i restauració + Inicia sessió + Nom d\'usuari + Contrasenya + Velocitat de reproducció + Tons + Reprodueix-ho tot + Llistes de reproducció + Instància personalitzada + URL de l\'API + URL del servidor intermediari d\'imatges + URL no vàlid + Suprimeix la llista de reproducció + Suprimeix la llista i les cançons + Neteja la llista + Desactiva l\'historial de cerca + \ No newline at end of file diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 77bf76bc..50448f5a 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -84,4 +84,17 @@ Odstranit playlist a skladby Vymazat playlist Vypnout historii vyhledávání + URL API Hyperpipe + AMOLED + Barevné schéma + Motiv + Tmavý + Světlý + Systémový + Catppuccin + Ekvalizér + Předvolba ekvalizéru + Povolen + Žádný + Resetovat ekvalizér \ No newline at end of file diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml new file mode 100644 index 00000000..b8ab139e --- /dev/null +++ b/app/src/main/res/values-da/strings.xml @@ -0,0 +1,84 @@ + + + Afspil Sang + Fjern Sang + README + Tjek Repository og README + Seneste Udgivelse + Indsend fejlrapporter og ønsket funktioner + Version, Kontakt + Favoritter + Føj til Kø + Ryd Søgning + Gentag Alle + Gentag Én + Afspiller Nu + Sangmuligheder + Luk Afspiller + Afspil + Spring Næste Over + Fjern Element Fra Kø + Afspillerkø + Bland + Netværksindstillinger + Skift server + Sikkerhedskopiér og Gendan + Sikkerhedskopiér din sangliste, eller gendan en gammel sikkerhedskopi. + Sikkerhedskopiér Database + Gendan Database + Albumcover + Kunstner-avatar + Vis Kø + Brugernavn + Adgangskode + Nylige Sange + Luk Søgning + Piped Musik + Lokal Musik + Album + Ændr størrelse på musikcache + Grænse for musikcache + Afspilningshastighed + Tonehøjde + Annullér + Udseende Indstillinger + Tilpas udseendet efter dine behov. + Gå tilbage til systemets/temaets accentfarve som notifikationsminiature, hvis der ikke er nogen. + Notifikationsminiature-fallback + Føj til bibliotek + Tilføjede alle sangene til biblioteket + Føj alle sange til biblioteket + Ryd Kø + Playlister + Ryd Playliste + Indstillinger + Sange + Om + Nuværende Version + Github Issue + Søg efter en Sang + Søg Sange + Afspil Næste + Indstillinger + Luk Kø + Noget Gik Galt + Skift server for at løse afspilningsproblemer + Opret en sikkerhedskopi-fil + Gendan en tidligere sikkerhedskopi-fil + Vælg Server + Kunstner + Log ind + Kunstnere + OK + Ubegrænset + Afspil alle + Omvendt + Brugerdefineret instans + Ugyldig URL + Slet Playliste + Slet playliste og sange + Deaktivér søgehistorik + Gentag Slået Fra + Sangens Albumcover + Sæt på Pause + \ No newline at end of file diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml index 36023c3d..1777e015 100644 --- a/app/src/main/res/values-de-rDE/strings.xml +++ b/app/src/main/res/values-de-rDE/strings.xml @@ -53,4 +53,44 @@ Passwort Aktuelle Lieder Suche schließen + Benutzerdefinierte Instanz + Ungültige URL + Wiedergabegeschwindigkeit + Tonhöhe + Rückwärts + System + Hell + Dunkel + Alles abspielen + Sortierung + Abbrechen + Zur Bibliothek hinzufügen + Alle Titel zur Bibliothek hinzufügen + Amoled + Farbschema + Thema + Catppuccin + Darstellungseinstellungen + Suchverlauf deaktivieren + Lokale Musik + Alben + Künstler + OK + Größe des Musikcaches ändern + Unbegrenzt + Alle Titel wurden zur Bibliothek hinzugefügt + Wiedergabeliste leeren + Wiedergabelisten + API-URL + URL des ­Bildproxys + Wiedergabeliste löschen + Wiedergabeliste und Titel löschen + Wiedergabeliste leeren + Passen Sie das Aussehen an Ihre Bedürfnisse an. + Hyperpipe API-URL + Piped-Musik + Aktiviert + Equalizer zurücksetzen + Equalizer + Equalizer Voreinstellung \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index e925deb4..02b4224e 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -84,4 +84,17 @@ Borrar lista de reproducción y canciones Borrar lista de reproducción Deshabilitar el historial de búsqueda + URL de la API Hyperpipe + Sistema + Claro + Oscuro + AMOLED + Catppuccin + Esquema de colores + Tema + Ecualizador + Preajuste del ecualizador + Activado + Ninguno + Restablecer ecualizador \ No newline at end of file diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 70001af4..ab89a9dd 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -84,4 +84,17 @@ Pildipuhvri url Kustuta esitusloend Ära salvesta otsinguajalugu + Hyperpipe\'i API URL + Süsteemi kujundus + Hele kujundus + Tume kujundus + AMOLED värvid + Catppuccin + Värviteema + Teema + Ekvalaiser + Ekvalaiseri eelseadistused + Kasutusel + Pole kasutusel + Lähtesta ekvalaiser \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index b5d88d72..93282b1b 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -83,4 +83,5 @@ Intensité Supprimer une liste de lecture et les chansons Effacer la liste de lecture + Désactiver l\'historique de recherche \ No newline at end of file diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index cc08f27e..2440d220 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -1,7 +1,100 @@ सेटिंग्स - मुझे पढ़ें - गीत - के बारे में + README + गाने + बारे में + प्लेलिस्ट + प्लेलिस्ट हटाएँ + प्लेलिस्ट और गाने हटाएं + प्लेलिस्ट हटाएं + कस्टम इंस्टेंस + असामान्य URL + GitHub मुद्दा + बग रिपोर्ट और सुविधा अनुरोध सबमिट करें + पसंदीदा + कतार में जोड़ें + गाना बजायें + गीत हटाएं + खोज साफ़ करें + गाने खोजें + कोई गाना खोजें + अगला बजायें + सेटिंग्स + सभी दोहराएं + एक दोहराएँ + अभी बज रहा है + गाने के विकल्प + गाने की एल्बम कला + विराम + बजायें + अगला छोड़ें + कतार से आइटम हटाएँ + कतार बंद करें + प्लेयर कतार + नेटवर्क सेटिंग्स + सर्वर बदलें + एक बैकअप फ़ाइल बनाएँ + पिछली बैकअप फ़ाइल पुनर्स्थापित करें + एल्बम कला + कलाकार का अवतार + सर्वर चुनें + कतार दिखाएँ + कलाकार + लॉग इन करें + उपयोक्तानाम + हाल के गाने + खोज बंद करें + Piped संगीत + स्थानीय संगीत + एल्बम + कलाकार + ठीक है + संगीत कैशे आकार बदलें + असीमित + पिच + सभी को बजाएं + क्रमबद्ध करें + रद्द करें + उलटा + दिखावट सेटिंग्स + लाइब्रेरी में जोड़ें + सभी गाने लाइब्रेरी में जोड़ दिए गए + सभी गाने लाइब्रेरी में जोड़ें + कतार साफ़ करें + खोज इतिहास अक्षम करें + रिपॉजिटरी और README की जाँच करें + प्लेयर बंद करें + मिश्रण + प्लेबैक समस्याओं को ठीक करने के लिए सर्वर बदलें + डेटाबेस पुनर्स्थापित करें + अपनी आवश्यकताओं के अनुरूप दिखावट को अनुकूलित करें। + Api Url + नवीनतम रिलीज + वर्तमान संस्करण + दोहराएँ बंद + कुछ गलत हो गया + बैकअप और पुनर्स्थापना + अपनी गीत सूची का बैकअप लें या पुराना बैकअप पुनर्स्थापित करें। + बैकअप डेटाबेस + पासवर्ड + सूचना थंबनेल फ़ॉलबैक + यदि कोई नहीं है तो सूचना थंबनेल के रूप में सिस्टम/थीम एक्सेंट रंग का उपयोग करके वापस आएं। + छवि प्रॉक्सी URL + संस्करण, संपर्क करें + संगीत कैशे सीमा + प्लेबैक गति + हाइपरपाइप API की URL + सिस्टम + हल्की + AMOLED + गहरी + कैटपुचिन + रंग योजना + थीम + तुल्यकारक प्रीसेट + सक्रिय + कोई नहीं + तुल्यकारक + तुल्यकारक रीसेट करें \ No newline at end of file diff --git a/app/src/main/res/values-ia/strings.xml b/app/src/main/res/values-ia/strings.xml index d110e7dc..a2f07c7d 100644 --- a/app/src/main/res/values-ia/strings.xml +++ b/app/src/main/res/values-ia/strings.xml @@ -67,7 +67,7 @@ Repetition disactivate Musica local Adder al cauda - Problemas in GitHun + Problemas in GitHub Addeva tote le cantos al bibliotheca Face un copia de securitate de tu lista de cantos o restaura un copia de securitate ancian. Clauder le recerca @@ -84,4 +84,12 @@ Deler lista de reproduction e cantos Rader lista de reproduction Disactivar le chronologia de recerca + URL del API Hyperpipe + Thema + Systema + Clar + Obscur + AMOLED + Catppuccin + Schema de colores \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml new file mode 100644 index 00000000..94fd984b --- /dev/null +++ b/app/src/main/res/values-it/strings.xml @@ -0,0 +1,87 @@ + + + Brani + Informazioni + Controlla il codice sorgente e il LEGGIMI + Ultima pubblicazione + Attuale versione + Segnalazioni su GitHub + Versione, Contatti + Aggiungi alla coda + Riproduci brano + Rimuovi brano + Rimuovi ricerche + Cerca brani + Riproduci successivo + Impostazioni + Chiudi coda + Coda del riproduttore + Mischia + Cambia server per sistemare i problemi di riproduzione + Cambia server + Backup e ripristino + Ripristina un vecchio file di backup + Database di ripristino + Copertina dell\'album + Avatar dell\'artista + Mostra coda + Artista + Login + Nome utente + Password + Brani recenti + Chiudi ricerca + Musica su Piped + Musica locale + Album + Artisti + Limite della cache per la musica + Illimitato + Riproduci tutto + Ordina + Annulla + Inverso + Impostazioni di aspetto + Fallback per l\'immagine nella notifica + Aggiunti tutti i brani alla libreria + Rimuovi coda + URL non valido + Rimuovi playlist e brani + Svuota playlist + Disabilita la cronologia delle ricerche + Impostazioni + LEGGIMI + Invia segnalazioni di errori e richieste di funzionalità + Preferiti + Cerca un brano + Non ripetere + Ripeti tutti + Ripeti uno + In riproduzione + Opzioni del brano + Chiudi riproduttore + Copertina dell\'album del brano + Pausa + Riproduci + Salta prossimo + Rimuovi brano dalla coda + Qualcosa è andato storto + Impostazioni di rete + Fai il backup della lista dei tuoi brani o fai il ripristino da un vecchio backup. + Database del backup + Crea un file di backup + Seleziona server + Ok + Cambia la dimensione della cache per la musica + Velocità di riproduzione + Personalizza l\'aspetto per soddisfare le tue necessità. + Aggiungi alla libreria + Aggiunti tutti i brani alla libreria + Playlist + Istanza personalizzata + URL delle API + URL proxy delle immagini + Rimuovi playlist + Tono + Torna all\'utilizzo del colore di accento del sistema/tema come miniatura di notifica se non ce n\'è uno. + \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml new file mode 100644 index 00000000..2b574691 --- /dev/null +++ b/app/src/main/res/values-ja/strings.xml @@ -0,0 +1,23 @@ + + + 設定 + 最新リリース + 現在のバージョン + GitHub Issue + お気に入り + キューに追加 + 曲を再生 + 曲を削除 + 全てリピート + 再生中 + 詳細 + リポジトリとREADMEを確認 + バグ報告や機能リクエストを送信 + バージョン、連絡先 + README + 次に再生 + 曲を検索 + 曲を検索 + 設定 + 繰り返しオフ + \ 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 new file mode 100644 index 00000000..728195a6 --- /dev/null +++ b/app/src/main/res/values-pt/strings.xml @@ -0,0 +1,94 @@ + + + Tocando agora + Instância personalizada + Url da Api + Url do proxy de imagem + Url inválida + Apagar lista de reprodução + Apagar lista de reprodução e músicas + Limpar lista de reprodução + Desativar histórico de pesquisa + URL da API do Hyperpipe + Claro + Esquema de cores + Tema + Configurações + Músicas + Sobre + README + Verifique o repositório e o README + Última Versão + Versão Atual + Relatar problemas no GitHub + Enviar relatórios de bugs e solicitações de recursos + Versão, Contato + Favoritos + Adicionar à fila + Tocar música + Remover Música + Limpar Pesquisa + Procura de Músicas + Procure uma música + Tocar a próxima + Configurações + Repetir desativado + Repetir tudo + Repetir esta + Opções da música + Fechar reprodutor + Capa do álbum da música + Pausa + Tocar + Pular para o próximo + Remover Item da fila + Fechar fila + Tocar fila + Aleatório + Algo deu errado + Configurações de rede + Alterar servidor para corrigir problemas de reprodução + Alterar servidor + Backup e Restauração + Faça backup da sua lista de músicas ou restaure um backup antigo. + Banco de dados de backup + Criar um arquivo de backup + Restaurar um arquivo de backup anterior + Restaurar banco de dados + Capa do álbum + Avatar do Artista + Selecione o Servidor + Mostrar fila + Artista + Entrar + Nome de utilizador + Palavra-passe + Músicas recentes + Fechar pesquisa + Música canalizada + Música local + Álbuns + Artistas + Ok + Alterar o tamanho do cache de música + Limite de cache de música + Ilimitado + Velocidade de reprodução + Tom + Tocar tudo + Ordem de classificação + Cancelar + Volte a usar a cor de destaque do sistema/tema como uma miniatura de notificação, se não houver nenhuma. + Reverso + Configurações de aparência + Personalize a aparência para atender às suas necessidades. + Miniatura de notificação + Adicionar à biblioteca + Adicionadas todas as músicas à biblioteca + Adicione todas as músicas à biblioteca + Limpar fila + Listas de reprodução + Sistema + Escuro + AMOLED + \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 24856dd2..24335d14 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -84,4 +84,17 @@ Удалить плейлист и его содержимое Опустошить плейлист Выключить историю поиска + Hyperpipe API URL + Чёрная + Как в системе + Светлая + Тёмная + Цветовая схема + Тема + Catppuccin + Эквалайзер + Предустановка + Включен + Нет + Сбросить настройку \ No newline at end of file diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index caed4592..fb1dd668 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -2,13 +2,13 @@ Омиљено Затвори плејер - Затвори ред + Затвори редослед Направите фајл резервне копије Најновије издање Лозинка Омот албума Подешавања мреже - Уклони предмет из реда + Уклони предмет из редоследа Недавне песме Понављај све Пошаљите извештаје о грешкама и захтеве за функције @@ -36,9 +36,9 @@ Албуми База података резервне копије Извођач - Ред плејера + Редослед плејера Опције песме - Прикажи ред + Прикажи редослед Аватар извођача Мешање Проверите репозиторијум и README @@ -54,7 +54,7 @@ Пусти Понављање искључено Локална музика - Додај у ред + Додај у редослед GitHub пријава проблема Направите резервну копију плејлиста или вратите стару резервну копију. Затвори претрагу @@ -74,7 +74,7 @@ Додај све песме у библиотеку Додај у библиотеку Додате све песме у библиотеку - Очисти ред + Очисти редослед Неважећа URL адреса URL адреса API-ја URL адреса проксија слике @@ -84,4 +84,17 @@ Избриши плејлисту и песме Очисти плејлисту Онемогући историју претраге + Hyperpipe URL API-ја + Светла + Шема боја + Тема + Тамна + AMOLED + Catppuccin + Системски + Еквилајзер + Омогућено + Унапред подешен еквилајзер + Ниједно + Ресетуј еквилајзер \ No newline at end of file diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 21ceff92..97f91f76 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -82,4 +82,19 @@ Tüm şarkılar kütüphaneye eklendi Tüm şarkıları kütüphaneye ekle Piped Music + Özel örnek + Arama geçmişini devre dışı bırak + Hyperpipe API URL\'si + Sistem + Açık + AMOLED + Koyu + Catppuccin + Renk Düzeni + Tema + Etkin + Hiçbiri + Dengeleyiciyi sıfırla + Dengeleyici + Dengeleyici Ön Ayarı \ No newline at end of file diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 40bdec6c..08b1f937 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -84,4 +84,17 @@ Власний сервер Очистити список відтворення Вимкнути історію пошуку + Hyperpipe API URL + Еквалайзер + Передустановка еквалайзера + Ввімкнено + Нічого + Скинути еквалайзер + Системна + Світла + Темна + Чорна + Catppuccin + Колірна гамма + Тема \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 81f3c854..99c3fb47 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -84,4 +84,17 @@ 删除播放列表与其中的歌曲 清空播放列表 禁用搜索历史 + Hyperpipe API URL + 系统 + 浅色 + 暗色 + AMOLED 纯黑 + Catppuccin 配色 + 颜色主题 + 主题 + 均衡器 + 均衡器预设 + 已启用 + + 重设均衡器 \ 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 776c27e3..521ab694 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -85,4 +85,17 @@ Delete playlist and songs Clear Playlist Disable search history + Hyperpipe API URL + System + Light + Dark + AMOLED + Catppuccin + Color Scheme + Theme + Equalizer + Equalizer Preset + Enabled + None + Reset equalizer diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index b19d71aa..c5574237 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,7 +1,7 @@ -