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 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- xmlns:android
-
- ^$
-
-
-
-
-
-
-
-
- xmlns:.*
-
- ^$
-
-
- BY_NAME
-
-
-
-
-
-
- .*:id
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- .*:name
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- name
-
- ^$
-
-
-
-
-
-
-
-
- style
-
- ^$
-
-
-
-
-
-
-
-
- .*
-
- ^$
-
-
- BY_NAME
-
-
-
-
-
-
- .*
-
- http://schemas.android.com/apk/res/android
-
-
- ANDROID_ATTRIBUTE_ORDER
-
-
-
-
-
-
- .*
-
- .*
-
-
- BY_NAME
-
-
-
-
-
-
-
-
-
-
\ 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 @@
-