From e061007e1d265dec4ba083f31793a8fa8f0dd26b Mon Sep 17 00:00:00 2001 From: Outlet7493 <122801553+Outlet7493@users.noreply.github.com> Date: Mon, 15 Jan 2024 23:32:37 +0000 Subject: [PATCH 01/17] fix crash due to missing permission --- app/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 115c17e..508ea44 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,6 +5,7 @@ + From 09b307aecefcade75fb654015e5b2c1a449439b1 Mon Sep 17 00:00:00 2001 From: Outlet7493 <122801553+Outlet7493@users.noreply.github.com> Date: Mon, 15 Jan 2024 23:32:55 +0000 Subject: [PATCH 02/17] typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 09424dd..976b73a 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Spotify Premium account is **REQUIRED***. Offline caching, DRM bypassing, or raw - Basic playback with Spotify Connect support (Spotify Connect support is actually WIP) - Fairly optimized R8 rules, providing the release APKs with a size of 4-5mb (with the playback and protobuf parts!) -## 📸 Screentshots +## 📸 Screenshots
image From 7059e50adeda855b8fb0b04b2e146c1aa46e5173 Mon Sep 17 00:00:00 2001 From: Outlet7493 <122801553+Outlet7493@users.noreply.github.com> Date: Mon, 15 Jan 2024 23:33:34 +0000 Subject: [PATCH 03/17] bump hilt; optimize imports; add http logger for debug --- app/build.gradle.kts | 7 ++++--- .../itaysonlab/jetispot/core/di/ApiModule.kt | 6 ++++++ .../jetispot/ui/screens/yourlibrary2/YlRenderer.kt | 12 ++++++++++-- build.gradle.kts | 2 +- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a6204b8..23f7955 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,6 +1,6 @@ +import com.google.protobuf.gradle.* import java.io.FileInputStream import java.util.Properties -import com.google.protobuf.gradle.* plugins { id("com.android.application") @@ -30,7 +30,7 @@ val room_version: String by rootProject.extra val librespot_commit: String by rootProject.extra val hilt_version: String by rootProject.extra -val keystorePropertiesFile = rootProject.file("keystore.properties") +val keystorePropertiesFile: File = rootProject.file("keystore.properties") val splitApks = !project.hasProperty("noSplits") @@ -145,7 +145,7 @@ android { disable.addAll(listOf("MissingTranslation", "ExtraTranslation")) } - packagingOptions { + packaging { resources { excludes += "/META-INF/*.kotlin_module" excludes += "/META-INF/*.version" @@ -229,6 +229,7 @@ dependencies { implementation("com.squareup.retrofit2:retrofit:2.9.0") implementation("com.squareup.retrofit2:converter-moshi:2.9.0") implementation("com.squareup.retrofit2:converter-protobuf:2.9.0") + implementation("com.squareup.okhttp3:logging-interceptor:4.12.0") // Data - SQL implementation("androidx.room:room-runtime:$room_version") diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/di/ApiModule.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/di/ApiModule.kt index 710c57d..8329646 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/di/ApiModule.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/di/ApiModule.kt @@ -13,6 +13,7 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory import retrofit2.converter.protobuf.ProtoConverterFactory @@ -59,6 +60,11 @@ object ApiModule { }.build()) } } + if (BuildConfig.DEBUG) { + addInterceptor(HttpLoggingInterceptor().apply { + setLevel(HttpLoggingInterceptor.Level.BODY) + }) + } }.build() @Provides diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YlRenderer.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YlRenderer.kt index 3ea50b4..955bb54 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YlRenderer.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YlRenderer.kt @@ -7,7 +7,10 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.* +import androidx.compose.material.icons.rounded.Favorite +import androidx.compose.material.icons.rounded.Photo +import androidx.compose.material.icons.rounded.Podcasts +import androidx.compose.material.icons.rounded.PushPin import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -19,7 +22,12 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import bruhcollective.itaysonlab.jetispot.R -import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.* +import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.CollectionAlbum +import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.CollectionArtist +import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.CollectionEntry +import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.CollectionPinnedItem +import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.CollectionShow +import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.PredefCeType import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.rootlist.CollectionRootlistItem import bruhcollective.itaysonlab.jetispot.ui.shared.ImagePreview import bruhcollective.itaysonlab.jetispot.ui.shared.PreviewableAsyncImage diff --git a/build.gradle.kts b/build.gradle.kts index d926a48..99942c5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,7 +11,7 @@ buildscript { val room_version by extra("2.6.1") val librespot_commit by extra("6244f91aeb") - val hilt_version by extra("2.49") + val hilt_version by extra("2.50") } plugins { From 2fe35449a57b32afba9eb12de0151ae539f3079b Mon Sep 17 00:00:00 2001 From: Outlet7493 <122801553+Outlet7493@users.noreply.github.com> Date: Mon, 15 Jan 2024 23:52:34 +0000 Subject: [PATCH 04/17] bump kotlin, ksp, moshix --- .idea/kotlinc.xml | 2 +- app/build.gradle.kts | 6 +++--- build.gradle.kts | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index fdf8d99..8d81632 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 23f7955..1aafeba 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -5,12 +5,12 @@ import java.util.Properties plugins { id("com.android.application") id("org.jetbrains.kotlin.android") - id("dev.zacsweers.moshix") version "0.24.0" + id("dev.zacsweers.moshix") version "0.25.1" id("com.google.devtools.ksp") id("dagger.hilt.android.plugin") id("kotlin-kapt") id("com.google.protobuf") version "0.9.0" - kotlin("plugin.serialization") version "1.9.0" + kotlin("plugin.serialization") version "1.9.22" } apply(plugin = "dagger.hilt.android.plugin") @@ -181,7 +181,7 @@ dependencies { coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4") implementation("androidx.core:core-ktx:1.12.0") implementation("androidx.palette:palette-ktx:1.0.0") - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") implementation("androidx.appcompat:appcompat:1.7.0-alpha03") // Compose diff --git a/build.gradle.kts b/build.gradle.kts index 99942c5..32c68ad 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ buildscript { val compose_version by extra("1.5.0") val compose_m3_version by extra("1.1.2") - val compose_compiler_version by extra("1.5.1") + val compose_compiler_version by extra("1.5.8") val media2_version by extra("1.2.1") val accompanist_version by extra("0.33.0-alpha") @@ -17,10 +17,10 @@ buildscript { plugins { id("com.android.application") version "8.1.4" apply false id("com.android.library") version "8.1.4" apply false - id("org.jetbrains.kotlin.android") version "1.9.0" apply false - id("com.google.dagger.hilt.android") version "2.49" apply false - id("com.google.devtools.ksp") version "1.9.0-1.0.13" apply false - kotlin("plugin.serialization") version "1.9.0" apply false + id("org.jetbrains.kotlin.android") version "1.9.22" apply false + id("com.google.dagger.hilt.android") version "2.50" apply false + id("com.google.devtools.ksp") version "1.9.22-1.0.16" apply false + kotlin("plugin.serialization") version "1.9.22" apply false } tasks.register("clean", Delete::class) { From 173a4c75411fb85106dd43d33b2294b1a4203d62 Mon Sep 17 00:00:00 2001 From: Outlet7493 <122801553+Outlet7493@users.noreply.github.com> Date: Tue, 16 Jan 2024 20:39:03 +0000 Subject: [PATCH 05/17] support html for playlist descriptions (e.g. daylist) --- .../itaysonlab/jetispot/ui/hub/HubBinder.kt | 29 ++++++++++++++++- .../ui/hub/components/PlaylistHeader.kt | 32 ++++++++++++++----- 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/HubBinder.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/HubBinder.kt index 2fb1647..5d4b8be 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/HubBinder.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/HubBinder.kt @@ -10,7 +10,34 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubComponent import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubItem -import bruhcollective.itaysonlab.jetispot.ui.hub.components.* +import bruhcollective.itaysonlab.jetispot.ui.hub.components.AlbumHeader +import bruhcollective.itaysonlab.jetispot.ui.hub.components.AlbumTrackRow +import bruhcollective.itaysonlab.jetispot.ui.hub.components.ArtistHeader +import bruhcollective.itaysonlab.jetispot.ui.hub.components.ArtistPinnedItem +import bruhcollective.itaysonlab.jetispot.ui.hub.components.ArtistTrackRow +import bruhcollective.itaysonlab.jetispot.ui.hub.components.Carousel +import bruhcollective.itaysonlab.jetispot.ui.hub.components.CollectionHeader +import bruhcollective.itaysonlab.jetispot.ui.hub.components.EpisodeListItem +import bruhcollective.itaysonlab.jetispot.ui.hub.components.FindCard +import bruhcollective.itaysonlab.jetispot.ui.hub.components.GridMediumCard +import bruhcollective.itaysonlab.jetispot.ui.hub.components.HomeSectionHeader +import bruhcollective.itaysonlab.jetispot.ui.hub.components.HomeSectionLargeHeader +import bruhcollective.itaysonlab.jetispot.ui.hub.components.ImageRow +import bruhcollective.itaysonlab.jetispot.ui.hub.components.LargePlaylistHeader +import bruhcollective.itaysonlab.jetispot.ui.hub.components.LargerRow +import bruhcollective.itaysonlab.jetispot.ui.hub.components.LikedSongsRow +import bruhcollective.itaysonlab.jetispot.ui.hub.components.MediumCard +import bruhcollective.itaysonlab.jetispot.ui.hub.components.OutlineButton +import bruhcollective.itaysonlab.jetispot.ui.hub.components.PlaylistHeader +import bruhcollective.itaysonlab.jetispot.ui.hub.components.PlaylistTrackRow +import bruhcollective.itaysonlab.jetispot.ui.hub.components.PlaylistTrackRowLarger +import bruhcollective.itaysonlab.jetispot.ui.hub.components.PodcastTopicsStrip +import bruhcollective.itaysonlab.jetispot.ui.hub.components.SectionHeader +import bruhcollective.itaysonlab.jetispot.ui.hub.components.ShortcutsCard +import bruhcollective.itaysonlab.jetispot.ui.hub.components.ShortcutsContainer +import bruhcollective.itaysonlab.jetispot.ui.hub.components.ShowHeader +import bruhcollective.itaysonlab.jetispot.ui.hub.components.SingleFocusCard +import bruhcollective.itaysonlab.jetispot.ui.hub.components.TextRow //TODO: FIX UNSUPPORTED ID IN LISTENING HISTORY - BOBBYESP diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/PlaylistHeader.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/PlaylistHeader.kt index dad9f9e..81c732a 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/PlaylistHeader.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/PlaylistHeader.kt @@ -1,9 +1,21 @@ package bruhcollective.itaysonlab.jetispot.ui.hub.components +import android.text.method.LinkMovementMethod +import android.widget.TextView import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +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.layout.size +import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Language @@ -24,8 +36,9 @@ import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.text.HtmlCompat import bruhcollective.itaysonlab.jetispot.R -import bruhcollective.itaysonlab.jetispot.SpApp import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubItem import bruhcollective.itaysonlab.jetispot.ui.hub.LocalHubScreenDelegate import bruhcollective.itaysonlab.jetispot.ui.hub.components.essentials.EntityActionStrip @@ -136,13 +149,16 @@ fun LargePlaylistHeader( } if (!item.text?.subtitle.isNullOrEmpty()) { - Text( - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), - fontSize = 12.sp, - lineHeight = 18.sp, - text = item.text?.subtitle!!, modifier = Modifier + AndroidView( + modifier = Modifier .padding(horizontal = 16.dp) - .padding(top = 16.dp) + .padding(top = 16.dp), + factory = { ctx -> + TextView(ctx).apply { + text = HtmlCompat.fromHtml(item.text?.subtitle!!, HtmlCompat.FROM_HTML_MODE_COMPACT) + movementMethod = LinkMovementMethod.getInstance() + } + } ) } From c1435e36e6944b6021448d44536d3a954d7654d5 Mon Sep 17 00:00:00 2001 From: Outlet7493 <122801553+Outlet7493@users.noreply.github.com> Date: Tue, 16 Jan 2024 23:02:48 +0000 Subject: [PATCH 06/17] don't render playlists with empty names --- .../itaysonlab/jetispot/ui/screens/yourlibrary2/YlRenderer.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YlRenderer.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YlRenderer.kt index 955bb54..c8f9795 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YlRenderer.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YlRenderer.kt @@ -120,6 +120,7 @@ fun YLRRootlist( item: CollectionRootlistItem, modifier: Modifier ) { + if (item.name.isEmpty()) return YLRGenericItem( picUrl = item.picture, picCircle = false, From 4bb1efad843567f8019aae8e1f2742006ad22b0b Mon Sep 17 00:00:00 2001 From: Outlet7493 <122801553+Outlet7493@users.noreply.github.com> Date: Wed, 17 Jan 2024 18:48:56 +0000 Subject: [PATCH 07/17] fix usernames on playlist screen and save playlist images if seen on homescreen --- .../core/collection/SpCollectionManager.kt | 17 ++++- .../core/collection/SpCollectionWriter.kt | 28 +++++--- .../core/collection/db/LocalCollectionDao.kt | 23 +++++- .../jetispot/core/di/CollectionModule.kt | 6 -- .../itaysonlab/jetispot/ui/AppNavigation.kt | 1 - .../itaysonlab/jetispot/ui/dac/DacDelegate.kt | 5 ++ .../itaysonlab/jetispot/ui/dac/DacRender.kt | 45 +++++++++--- .../components_home/SectionComponentBinder.kt | 42 ++++++++--- .../SectionHeaderComponentBinder.kt | 4 +- .../ui/dac/components_home/ShortcutsBinder.kt | 70 +++++++++++++------ .../itaysonlab/jetispot/ui/hub/HubBinder.kt | 2 +- .../ui/hub/virt/PlaylistEntityView.kt | 25 +++++-- .../ui/screens/config/StorageScreen.kt | 50 ++++++++++--- .../ui/screens/dac/DacRendererScreen.kt | 27 ++++++- .../jetispot/ui/screens/hub/HubExt.kt | 17 ++++- .../jetispot/ui/screens/hub/HubScreen.kt | 8 +-- .../jetispot/ui/screens/hub/PlaylistScreen.kt | 6 +- .../ui/screens/yourlibrary2/YlDelegate.kt | 11 +++ .../ui/screens/yourlibrary2/YlRenderer.kt | 14 +++- .../YourLibraryContainerScreen.kt | 48 ++++++++++--- app/src/main/res/values/strings.xml | 2 + 21 files changed, 352 insertions(+), 99 deletions(-) create mode 100644 app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YlDelegate.kt diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/collection/SpCollectionManager.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/collection/SpCollectionManager.kt index fc5720d..348e1b0 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/collection/SpCollectionManager.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/collection/SpCollectionManager.kt @@ -1,15 +1,19 @@ package bruhcollective.itaysonlab.jetispot.core.collection import bruhcollective.itaysonlab.jetispot.core.SpMetadataRequester -import bruhcollective.itaysonlab.jetispot.core.util.Log import bruhcollective.itaysonlab.jetispot.core.SpSessionManager import bruhcollective.itaysonlab.jetispot.core.api.SpCollectionApi import bruhcollective.itaysonlab.jetispot.core.api.SpInternalApi import bruhcollective.itaysonlab.jetispot.core.collection.db.LocalCollectionDao import bruhcollective.itaysonlab.jetispot.core.collection.db.LocalCollectionRepository +import bruhcollective.itaysonlab.jetispot.core.util.Log import bruhcollective.itaysonlab.swedentricks.protos.CollectionUpdate import com.spotify.playlist4.Playlist4ApiProto -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import xyz.gianlu.librespot.common.Utils import xyz.gianlu.librespot.dealer.DealerClient import java.util.concurrent.Executors @@ -87,6 +91,15 @@ class SpCollectionManager @Inject constructor( writer.performRemove(id, set) } + suspend fun getRootlistImage( + uri: String + ) = dao.getRootlistImage(uri) + + suspend fun clearRootlist() { + dao.deleteRootList() + dao.deleteCollectionCategory("rootlist") + } + override fun onMessage(p0: String, p1: MutableMap, p2: ByteArray) { if (p0.startsWith("hm://playlist/v2/user/")) { writer.pubsubUpdateRootlist(Playlist4ApiProto.PlaylistModificationInfo.parseFrom(p2)) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/collection/SpCollectionWriter.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/collection/SpCollectionWriter.kt index 79e24cd..d51fa2c 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/collection/SpCollectionWriter.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/collection/SpCollectionWriter.kt @@ -1,29 +1,41 @@ package bruhcollective.itaysonlab.jetispot.core.collection import bruhcollective.itaysonlab.jetispot.core.SpMetadataRequester -import bruhcollective.itaysonlab.jetispot.core.util.Log import bruhcollective.itaysonlab.jetispot.core.SpSessionManager import bruhcollective.itaysonlab.jetispot.core.api.SpCollectionApi import bruhcollective.itaysonlab.jetispot.core.api.SpInternalApi import bruhcollective.itaysonlab.jetispot.core.collection.db.LocalCollectionDao import bruhcollective.itaysonlab.jetispot.core.collection.db.LocalCollectionRepository -import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.* +import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.CollectionAlbum +import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.CollectionArtist +import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.CollectionContentFilter +import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.CollectionEpisode +import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.CollectionPinnedItem +import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.CollectionShow +import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.CollectionTrack import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.rootlist.CollectionRootlistItem import bruhcollective.itaysonlab.jetispot.core.objs.tags.ContentFilterResponse +import bruhcollective.itaysonlab.jetispot.core.util.Log import bruhcollective.itaysonlab.jetispot.core.util.Revision import bruhcollective.itaysonlab.jetispot.core.util.SpUtils import bruhcollective.itaysonlab.swedentricks.protos.CollectionUpdate import bruhcollective.itaysonlab.swedentricks.protos.CollectionUpdateEntry import com.google.protobuf.ByteString import com.spotify.collection2.v2.proto.Collection2V2 -import com.spotify.extendedmetadata.ExtendedMetadata import com.spotify.extendedmetadata.ExtensionKindOuterClass import com.spotify.metadata.Metadata import com.spotify.playlist4.Playlist4ApiProto -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch import xyz.gianlu.librespot.common.Utils -import xyz.gianlu.librespot.metadata.* -import java.nio.charset.StandardCharsets +import xyz.gianlu.librespot.metadata.AlbumId +import xyz.gianlu.librespot.metadata.ArtistId +import xyz.gianlu.librespot.metadata.EpisodeId +import xyz.gianlu.librespot.metadata.ImageId +import xyz.gianlu.librespot.metadata.PlaylistId +import xyz.gianlu.librespot.metadata.ShowId +import xyz.gianlu.librespot.metadata.TrackId class SpCollectionWriter( private val spSessionManager: SpSessionManager, @@ -381,7 +393,7 @@ class SpCollectionWriter( timestamp = pair.first.attributes.timestamp, name = pair.second.attributes.name, ownerUsername = pair.second.ownerUsername, - picture = pair.second.attributes.pictureSizeList.find { it.targetName == "default" }?.url ?: "https://i.scdn.co/image/${Utils.bytesToHex(pair.second.attributes.picture).lowercase()}" + picture = pair.second.attributes.pictureSizeList.find { it.targetName == "default" }?.url ?: if (pair.second.attributes.picture.isEmpty) "" else "https://i.scdn.co/image/${Utils.bytesToHex(pair.second.attributes.picture).lowercase()}" ) }.toTypedArray()) } @@ -407,7 +419,7 @@ class SpCollectionWriter( timestamp = pair.first.attributes.timestamp, name = pair.second.attributes.name, ownerUsername = pair.second.ownerUsername, - picture = pair.second.attributes.pictureSizeList.find { it.targetName == "default" }?.url ?: "https://i.scdn.co/image/${Utils.bytesToHex(pair.second.attributes.picture).lowercase()}" + picture = pair.second.attributes.pictureSizeList.find { it.targetName == "default" }?.url ?: if (pair.second.attributes.picture.isEmpty) "" else "https://i.scdn.co/image/${Utils.bytesToHex(pair.second.attributes.picture).lowercase()}" ) }.toTypedArray() diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/collection/db/LocalCollectionDao.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/collection/db/LocalCollectionDao.kt index 12ca146..2b11805 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/collection/db/LocalCollectionDao.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/collection/db/LocalCollectionDao.kt @@ -1,10 +1,20 @@ package bruhcollective.itaysonlab.jetispot.core.collection.db -import androidx.room.* +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.RawQuery import androidx.sqlite.db.SimpleSQLiteQuery import androidx.sqlite.db.SupportSQLiteQuery import bruhcollective.itaysonlab.jetispot.core.collection.db.model.LocalCollectionCategory -import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.* +import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.CollectionAlbum +import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.CollectionArtist +import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.CollectionContentFilter +import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.CollectionEpisode +import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.CollectionPinnedItem +import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.CollectionShow +import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.CollectionTrack import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.rootlist.CollectionRootlistItem import kotlinx.coroutines.flow.Flow @@ -37,6 +47,9 @@ interface LocalCollectionDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun addEpisodes(vararg items: CollectionEpisode) + @Query("UPDATE rootlist SET picture = :picture WHERE uri = :uri AND CASE WHEN :overwrite THEN 1 ELSE picture <> '' END") + suspend fun updateRootlistPicture(uri: String, picture: String, overwrite: Boolean) + @Query("SELECT * from lcTypes WHERE type = :of") suspend fun getCollection(of: String): LocalCollectionCategory? @@ -52,6 +65,9 @@ interface LocalCollectionDao { @Query("DELETE FROM lcAlbums WHERE id IN (:ids)") suspend fun deleteAlbums(vararg ids: String) + @Query("DELETE FROM lcTypes WHERE type = :of") + suspend fun deleteCollectionCategory(of: String) + @Query("SELECT * from lcArtists ORDER BY addedAt DESC") suspend fun getArtists(): List @@ -73,6 +89,9 @@ interface LocalCollectionDao { @Query("SELECT * from rootlist ORDER BY timestamp DESC") suspend fun getRootlist(): List + @Query("SELECT picture FROM rootlist WHERE uri = :uri") + suspend fun getRootlistImage(uri: String): String? + @Query("DELETE from lcTracks") suspend fun deleteTracks() diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/di/CollectionModule.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/di/CollectionModule.kt index 4e5f78a..c3bf8ec 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/di/CollectionModule.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/di/CollectionModule.kt @@ -2,14 +2,8 @@ package bruhcollective.itaysonlab.jetispot.core.di import android.content.Context import androidx.room.Room -import bruhcollective.itaysonlab.jetispot.core.SpMetadataRequester -import bruhcollective.itaysonlab.jetispot.core.SpSessionManager -import bruhcollective.itaysonlab.jetispot.core.api.SpCollectionApi -import bruhcollective.itaysonlab.jetispot.core.api.SpInternalApi -import bruhcollective.itaysonlab.jetispot.core.collection.SpCollectionManager import bruhcollective.itaysonlab.jetispot.core.collection.db.LocalCollectionDao import bruhcollective.itaysonlab.jetispot.core.collection.db.LocalCollectionDatabase -import bruhcollective.itaysonlab.jetispot.core.collection.db.LocalCollectionRepository import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/AppNavigation.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/AppNavigation.kt index 4e4d59d..057b668 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/AppNavigation.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/AppNavigation.kt @@ -26,7 +26,6 @@ import bruhcollective.itaysonlab.jetispot.R import bruhcollective.itaysonlab.jetispot.core.SpAuthManager import bruhcollective.itaysonlab.jetispot.core.SpSessionManager import bruhcollective.itaysonlab.jetispot.core.api.SpInternalApi -import bruhcollective.itaysonlab.jetispot.core.util.Log import bruhcollective.itaysonlab.jetispot.ui.bottomsheets.jump_to_artist.JumpToArtistBottomSheet import bruhcollective.itaysonlab.jetispot.ui.screens.BottomSheet import bruhcollective.itaysonlab.jetispot.ui.screens.Dialog diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/DacDelegate.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/DacDelegate.kt index e760dc3..227d452 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/DacDelegate.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/DacDelegate.kt @@ -7,6 +7,11 @@ import com.spotify.dac.player.v1.proto.PlayCommand @Stable interface DacDelegate { fun dispatchPlay(command: PlayCommand) + suspend fun updateRootlistImage( + uri: String, + image: String, + overwrite: Boolean + ) } val LocalDacDelegate = staticCompositionLocalOf { error("DacDelegate should be initialized") } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/DacRender.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/DacRender.kt index 0686af0..fdad1bc 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/DacRender.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/DacRender.kt @@ -1,8 +1,6 @@ package bruhcollective.itaysonlab.jetispot.ui.dac import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -11,18 +9,49 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import bruhcollective.itaysonlab.jetispot.BuildConfig import bruhcollective.itaysonlab.jetispot.proto.ErrorComponent -import bruhcollective.itaysonlab.jetispot.ui.dac.components_home.* -import bruhcollective.itaysonlab.jetispot.ui.dac.components_plans.* +import bruhcollective.itaysonlab.jetispot.ui.dac.components_home.MediumActionCardBinder +import bruhcollective.itaysonlab.jetispot.ui.dac.components_home.RecentlyPlayedSectionComponentBinder +import bruhcollective.itaysonlab.jetispot.ui.dac.components_home.RecsplanationHeadingComponentBinder +import bruhcollective.itaysonlab.jetispot.ui.dac.components_home.RecsplanationHeadingSingleTextComponentBinder +import bruhcollective.itaysonlab.jetispot.ui.dac.components_home.SectionComponentBinder +import bruhcollective.itaysonlab.jetispot.ui.dac.components_home.SectionHeaderComponentBinder +import bruhcollective.itaysonlab.jetispot.ui.dac.components_home.ShortcutsBinder +import bruhcollective.itaysonlab.jetispot.ui.dac.components_home.SmallActionCardBinder +import bruhcollective.itaysonlab.jetispot.ui.dac.components_home.ToolbarComponent2Binder +import bruhcollective.itaysonlab.jetispot.ui.dac.components_home.ToolbarComponentBinder +import bruhcollective.itaysonlab.jetispot.ui.dac.components_plans.BenefitListComponentBinder +import bruhcollective.itaysonlab.jetispot.ui.dac.components_plans.DisclaimerComponentBinder +import bruhcollective.itaysonlab.jetispot.ui.dac.components_plans.FallbackPlanComponentBinder +import bruhcollective.itaysonlab.jetispot.ui.dac.components_plans.MultiUserMemberComponentBinder +import bruhcollective.itaysonlab.jetispot.ui.dac.components_plans.PlanComponentBinder +import bruhcollective.itaysonlab.jetispot.ui.dac.components_plans.SingleUserComponentBinder import com.google.protobuf.Message import com.spotify.allplans.v1.DisclaimerComponent import com.spotify.allplans.v1.PlanComponent import com.spotify.home.dac.component.heading.v1.proto.RecsplanationHeadingSingleTextComponent -import com.spotify.home.dac.component.v1.proto.* +import com.spotify.home.dac.component.v1.proto.AlbumCardActionsMediumComponent +import com.spotify.home.dac.component.v1.proto.AlbumCardActionsSmallComponent +import com.spotify.home.dac.component.v1.proto.ArtistCardActionsMediumComponent +import com.spotify.home.dac.component.v1.proto.ArtistCardActionsSmallComponent +import com.spotify.home.dac.component.v1.proto.PlaylistCardActionsMediumComponent +import com.spotify.home.dac.component.v1.proto.PlaylistCardActionsSmallComponent +import com.spotify.home.dac.component.v1.proto.RecentlyPlayedSectionComponent +import com.spotify.home.dac.component.v1.proto.RecsplanationHeadingComponent +import com.spotify.home.dac.component.v1.proto.SectionComponent +import com.spotify.home.dac.component.v1.proto.SectionHeaderComponent +import com.spotify.home.dac.component.v1.proto.ShortcutsSectionComponent +import com.spotify.home.dac.component.v1.proto.SnappyGridSectionComponent +import com.spotify.home.dac.component.v1.proto.ToolbarComponent import com.spotify.home.dac.component.v2.proto.ToolbarComponentV2 -import com.spotify.planoverview.v1.* +import com.spotify.planoverview.v1.BenefitListComponent +import com.spotify.planoverview.v1.FallbackPlanComponent +import com.spotify.planoverview.v1.MultiUserMemberComponent +import com.spotify.planoverview.v1.SingleUserPrepaidComponent +import com.spotify.planoverview.v1.SingleUserRecurringComponent +import com.spotify.planoverview.v1.SingleUserTrialComponent @Composable -fun DacRender ( +fun DacRender( item: Message ) { when (item) { @@ -40,7 +69,7 @@ fun DacRender ( // Home is ToolbarComponent -> ToolbarComponentBinder(item) is ToolbarComponentV2 -> ToolbarComponent2Binder(item) - is ShortcutsSectionComponent -> ShortcutsBinder(item) + is ShortcutsSectionComponent -> ShortcutsBinder(item) // e.g. small card playlist, episode, etc. on home screen is AlbumCardActionsSmallComponent -> SmallActionCardBinder(title = item.title, subtitle = item.subtitle, navigateUri = item.navigateUri, likeUri = item.likeUri, imageUri = item.imageUri, imagePlaceholder = "album", playCommand = item.playCommand) is ArtistCardActionsSmallComponent -> SmallActionCardBinder(title = item.title, subtitle = item.subtitle, navigateUri = item.navigateUri, likeUri = item.followUri, imageUri = item.imageUri, imagePlaceholder = "artist", playCommand = item.playCommand) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_home/SectionComponentBinder.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_home/SectionComponentBinder.kt index c514e80..72b559c 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_home/SectionComponentBinder.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_home/SectionComponentBinder.kt @@ -1,26 +1,43 @@ package bruhcollective.itaysonlab.jetispot.ui.dac.components_home -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +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.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp +import bruhcollective.itaysonlab.jetispot.ui.dac.LocalDacDelegate import bruhcollective.itaysonlab.jetispot.ui.ext.dynamicUnpack import bruhcollective.itaysonlab.jetispot.ui.shared.MediumText import bruhcollective.itaysonlab.jetispot.ui.shared.PreviewableAsyncImage import bruhcollective.itaysonlab.jetispot.ui.shared.Subtext import bruhcollective.itaysonlab.jetispot.ui.shared.navClickable -import com.spotify.home.dac.component.v1.proto.* +import com.spotify.home.dac.component.v1.proto.AlbumCardMediumComponent +import com.spotify.home.dac.component.v1.proto.ArtistCardMediumComponent +import com.spotify.home.dac.component.v1.proto.EpisodeCardMediumComponent +import com.spotify.home.dac.component.v1.proto.PlaylistCardMediumComponent +import com.spotify.home.dac.component.v1.proto.SectionComponent +import com.spotify.home.dac.component.v1.proto.ShowCardMediumComponent +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext @Composable fun SectionComponentBinder( item: SectionComponent ) { + val localDacDelegate = LocalDacDelegate.current + val list = item.componentsList.map { it.dynamicUnpack() } LazyRow(contentPadding = PaddingValues(horizontal = 16.dp), horizontalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier.fillMaxWidth()) { items(list) { listItem -> @@ -33,13 +50,20 @@ fun SectionComponentBinder( imagePlaceholder = "album" ) - is PlaylistCardMediumComponent -> MediumCard( - title = listItem.title, - subtitle = listItem.subtitle, - navigateUri = listItem.navigateUri, - imageUri = listItem.imageUri, - imagePlaceholder = "playlist" - ) + is PlaylistCardMediumComponent -> { + LaunchedEffect(Unit) { + withContext(Dispatchers.IO) { + localDacDelegate.updateRootlistImage(listItem.navigateUri, listItem.imageUri, overwrite = true) + } + } + MediumCard( + title = listItem.title, + subtitle = listItem.subtitle, + navigateUri = listItem.navigateUri, + imageUri = listItem.imageUri, + imagePlaceholder = "playlist" + ) + } is ArtistCardMediumComponent -> MediumCard( title = listItem.title, diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_home/SectionHeaderComponentBinder.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_home/SectionHeaderComponentBinder.kt index d46853d..5d0ac42 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_home/SectionHeaderComponentBinder.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_home/SectionHeaderComponentBinder.kt @@ -1,18 +1,16 @@ package bruhcollective.itaysonlab.jetispot.ui.dac.components_home -import androidx.compose.foundation.background import androidx.compose.foundation.layout.padding import androidx.compose.material.Text import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @Composable -fun SectionHeaderComponentBinder ( +fun SectionHeaderComponentBinder( text: String ) { Text(text = text, color = MaterialTheme.colorScheme.onSurface, fontWeight = FontWeight.Bold, fontSize = 21.sp, modifier = Modifier.padding(16.dp)) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_home/ShortcutsBinder.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_home/ShortcutsBinder.kt index 13493c9..345c0db 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_home/ShortcutsBinder.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_home/ShortcutsBinder.kt @@ -1,34 +1,53 @@ package bruhcollective.itaysonlab.jetispot.ui.dac.components_home -import androidx.compose.foundation.layout.* -import androidx.compose.material3.* +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import bruhcollective.itaysonlab.jetispot.ui.dac.LocalDacDelegate import bruhcollective.itaysonlab.jetispot.ui.ext.compositeSurfaceElevation import bruhcollective.itaysonlab.jetispot.ui.ext.dynamicUnpack import bruhcollective.itaysonlab.jetispot.ui.shared.PreviewableAsyncImage import bruhcollective.itaysonlab.jetispot.ui.shared.navClickable -import com.spotify.home.dac.component.v1.proto.* +import com.spotify.home.dac.component.v1.proto.AlbumCardShortcutComponent +import com.spotify.home.dac.component.v1.proto.ArtistCardShortcutComponent +import com.spotify.home.dac.component.v1.proto.EpisodeCardShortcutComponent +import com.spotify.home.dac.component.v1.proto.PlaylistCardShortcutComponent +import com.spotify.home.dac.component.v1.proto.ShortcutsSectionComponent +import com.spotify.home.dac.component.v1.proto.ShowCardShortcutComponent +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext @Composable fun ShortcutsBinder( item: ShortcutsSectionComponent ) { + val localDacDelegate = LocalDacDelegate.current + item.shortcutsList.map { it.dynamicUnpack() }.chunked(2).forEachIndexed { idx, pairs -> Row( - Modifier - .padding(horizontal = 16.dp) - .padding(bottom = if (idx != item.shortcutsList.lastIndex / 2) 8.dp else 0.dp) + Modifier + .padding(horizontal = 16.dp) + .padding(bottom = if (idx != item.shortcutsList.lastIndex / 2) 8.dp else 0.dp) ) { pairs.forEachIndexed { xIdx, xItem -> Box( - Modifier - .weight(1f) - .padding(end = if (xIdx == 0 && pairs.size == 2) 8.dp else 0.dp)) { + Modifier + .weight(1f) + .padding(end = if (xIdx == 0 && pairs.size == 2) 8.dp else 0.dp)) { when (xItem) { is AlbumCardShortcutComponent -> ShortcutComponentBinder( xItem.navigateUri, @@ -36,12 +55,19 @@ fun ShortcutsBinder( "album", xItem.title ) - is PlaylistCardShortcutComponent -> ShortcutComponentBinder( - xItem.navigateUri, - xItem.imageUri, - "playlist", - xItem.title - ) + is PlaylistCardShortcutComponent -> { + LaunchedEffect(Unit) { + withContext(Dispatchers.IO) { + localDacDelegate.updateRootlistImage(xItem.navigateUri, xItem.imageUri, overwrite = false) + } + } + ShortcutComponentBinder( + xItem.navigateUri, + xItem.imageUri, + "playlist", + xItem.title + ) + } is ShowCardShortcutComponent -> ShortcutComponentBinder( xItem.navigateUri, xItem.imageUri, @@ -74,14 +100,16 @@ private fun ShortcutComponentBinder( imagePlaceholder: String, title: String ) { + + Card( colors = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.compositeSurfaceElevation( 3.dp ) ), modifier = Modifier - .height(56.dp) - .fillMaxWidth() + .height(56.dp) + .fillMaxWidth() ) { Row(Modifier.navClickable { navController -> navController.navigate(navigateUri) @@ -98,10 +126,10 @@ private fun ShortcutComponentBinder( maxLines = 2, overflow = TextOverflow.Ellipsis, modifier = Modifier - .align( - Alignment.CenterVertically - ) - .padding(horizontal = 8.dp) + .align( + Alignment.CenterVertically + ) + .padding(horizontal = 8.dp) .fillMaxWidth(), ) } diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/HubBinder.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/HubBinder.kt index 5d4b8be..85d8bcc 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/HubBinder.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/HubBinder.kt @@ -42,7 +42,7 @@ import bruhcollective.itaysonlab.jetispot.ui.hub.components.TextRow //TODO: FIX UNSUPPORTED ID IN LISTENING HISTORY - BOBBYESP @Composable -fun HubBinder ( +fun HubBinder( item: HubItem, isRenderingInGrid: Boolean = false, ) { diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/virt/PlaylistEntityView.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/virt/PlaylistEntityView.kt index c455dad..b7d1e80 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/virt/PlaylistEntityView.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/virt/PlaylistEntityView.kt @@ -4,13 +4,25 @@ import android.text.format.DateUtils import bruhcollective.itaysonlab.jetispot.core.SpMetadataRequester import bruhcollective.itaysonlab.jetispot.core.SpSessionManager import bruhcollective.itaysonlab.jetispot.core.api.SpInternalApi -import bruhcollective.itaysonlab.jetispot.core.objs.hub.* -import bruhcollective.itaysonlab.jetispot.core.objs.player.* +import bruhcollective.itaysonlab.jetispot.core.collection.SpCollectionManager +import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubComponent +import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubEvent +import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubEvents +import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubImage +import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubImages +import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubItem +import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubResponse +import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubText +import bruhcollective.itaysonlab.jetispot.core.objs.player.PfcContextData +import bruhcollective.itaysonlab.jetispot.core.objs.player.PfcOptSkipTo +import bruhcollective.itaysonlab.jetispot.core.objs.player.PfcOptions +import bruhcollective.itaysonlab.jetispot.core.objs.player.PfcState +import bruhcollective.itaysonlab.jetispot.core.objs.player.PfcStateOptions +import bruhcollective.itaysonlab.jetispot.core.objs.player.PlayFromContextData +import bruhcollective.itaysonlab.jetispot.core.objs.player.PlayFromContextPlayerData import bruhcollective.itaysonlab.jetispot.core.tracks import bruhcollective.itaysonlab.jetispot.core.user -import bruhcollective.itaysonlab.jetispot.ui.screens.hub.LikedSongsViewModel import com.google.protobuf.ByteString -import com.google.protobuf.StringValue import com.spotify.metadata.Metadata import com.spotify.playlist4.Playlist4ApiProto import kotlinx.coroutines.Dispatchers @@ -27,7 +39,7 @@ object PlaylistEntityView { val hubResponse: HubResponse ) - suspend fun getPlaylistView(id: String, sessionManager: SpSessionManager, spInternalApi: SpInternalApi, spMetadataRequester: SpMetadataRequester): ApiPlaylist { + suspend fun getPlaylistView(id: String, sessionManager: SpSessionManager, spInternalApi: SpInternalApi, spMetadataRequester: SpMetadataRequester, spCollectionManager: SpCollectionManager): ApiPlaylist { val playlist = withContext(Dispatchers.IO) { sessionManager.session.api().getPlaylist(PlaylistId.fromUri("spotify:playlist:$id")) } val playlistTracks = playlist.contents.itemsList.distinctBy { it.uri }.filter { it.uri.startsWith("spotify:track:") } val playlistOwnerUsername = "spotify:user:${playlist.ownerUsername}" @@ -79,7 +91,8 @@ object PlaylistEntityView { uri = playlist.attributes.formatAttributesList.find { it.key == "image" }?.value ?: playlist.attributes.formatAttributesList.find { it.key == "image_url" }?.value ?: playlist.attributes.pictureSizeList.find { it.targetName == "default" }?.url - ?: if (playlist.attributes.hasPicture()) "https://i.scdn.co/image/${Utils.bytesToHex(playlist.attributes.picture).lowercase()}" else "" + ?: if (playlist.attributes.hasPicture()) "https://i.scdn.co/image/${Utils.bytesToHex(playlist.attributes.picture).lowercase()}" + else spCollectionManager.getRootlistImage("spotify:playlist:$id") ?: "" ) ) ) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/config/StorageScreen.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/config/StorageScreen.kt index 947a8c3..60b5e15 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/config/StorageScreen.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/config/StorageScreen.kt @@ -5,14 +5,37 @@ import android.text.format.Formatter import androidx.annotation.StringRes import androidx.compose.foundation.Canvas import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +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.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.* -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.material.icons.rounded.ArrowBack +import androidx.compose.material.icons.rounded.Cached +import androidx.compose.material.icons.rounded.Image +import androidx.compose.material.icons.rounded.Save +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeTopAppBar +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -22,7 +45,6 @@ import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp @@ -31,6 +53,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.ViewModel import bruhcollective.itaysonlab.jetispot.R import bruhcollective.itaysonlab.jetispot.core.SpSessionManager +import bruhcollective.itaysonlab.jetispot.core.collection.SpCollectionManager import bruhcollective.itaysonlab.jetispot.core.metadata_db.SpMetadataDb import bruhcollective.itaysonlab.jetispot.core.util.Device import bruhcollective.itaysonlab.jetispot.ui.ext.blendWith @@ -41,7 +64,11 @@ import bruhcollective.itaysonlab.jetispot.ui.shared.PagingLoadingPage import coil.annotation.ExperimentalCoilApi import coil.imageLoader import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.io.File import javax.inject.Inject @@ -236,7 +263,8 @@ fun MultiStateProgressIndicator( @HiltViewModel class StorageViewModel @Inject constructor( private val spSessionManager: SpSessionManager, - private val spMetadataDb: SpMetadataDb + private val spMetadataDb: SpMetadataDb, + private val spCollectionManager: SpCollectionManager ) : ViewModel() { val types = StorageFileKind.values() val clearActions = ClearAction.values() @@ -290,6 +318,10 @@ class StorageViewModel @Inject constructor( } ClearAction.ClearMetadata -> spMetadataDb.clear() + + ClearAction.ClearRootlist -> spCollectionManager.clearRootlist() + + ClearAction.ClearDb -> spCollectionManager.clean() } load(context) @@ -357,6 +389,8 @@ class StorageViewModel @Inject constructor( @StringRes val title: Int ) { ClearCaches(R.string.storage_clear), - ClearMetadata(R.string.storage_clear_metadata) + ClearMetadata(R.string.storage_clear_metadata), + ClearRootlist(R.string.clear_rootlist), + ClearDb(R.string.clear_db) } } diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/dac/DacRendererScreen.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/dac/DacRendererScreen.kt index 176dcd6..f697d0e 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/dac/DacRendererScreen.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/dac/DacRendererScreen.kt @@ -1,12 +1,22 @@ package bruhcollective.itaysonlab.jetispot.ui.screens.dac -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +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.material.icons.Icons import androidx.compose.material.icons.rounded.ArrowBack -import androidx.compose.material3.* +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeTopAppBar +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll @@ -15,6 +25,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.ViewModel import bruhcollective.itaysonlab.jetispot.core.SpPlayerServiceManager import bruhcollective.itaysonlab.jetispot.core.api.SpInternalApi +import bruhcollective.itaysonlab.jetispot.core.collection.db.LocalCollectionDao import bruhcollective.itaysonlab.jetispot.core.util.toApplicationPlayCommand import bruhcollective.itaysonlab.jetispot.proto.ErrorComponent import bruhcollective.itaysonlab.jetispot.ui.dac.DacDelegate @@ -120,6 +131,7 @@ fun DacRendererScreen( class DacViewModel @Inject constructor( private val spInternalApi: SpInternalApi, private val spPlayerServiceManager: SpPlayerServiceManager, + private val dao: LocalCollectionDao, private val moshi: Moshi ) : ViewModel(), DacDelegate { var facet = "default" @@ -165,6 +177,15 @@ class DacViewModel @Inject constructor( load(loader) } + // overwrite will update the image URI in db only if there isn't already one + override suspend fun updateRootlistImage( + uri: String, + image: String, + overwrite: Boolean + ) { + dao.updateRootlistPicture(uri, image, overwrite) + } + sealed class State { class Loaded(val sticky: Message?, val data: List) : State() class Error(val error: Exception) : State() diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/HubExt.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/HubExt.kt index 773c1f1..d5864e2 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/HubExt.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/HubExt.kt @@ -1,12 +1,24 @@ package bruhcollective.itaysonlab.jetispot.ui.screens.hub import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.ArrowBack -import androidx.compose.material3.* +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeTopAppBar +import androidx.compose.material3.MaterialTheme +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.runtime.CompositionLocalProvider import androidx.compose.runtime.rememberCoroutineScope @@ -111,7 +123,6 @@ fun HubScaffold( HubBinder(it) } } - } } } diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/HubScreen.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/HubScreen.kt index db60b67..8aef788 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/HubScreen.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/HubScreen.kt @@ -1,6 +1,9 @@ package bruhcollective.itaysonlab.jetispot.ui.screens.hub -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.LazyVerticalGrid @@ -20,7 +23,6 @@ import bruhcollective.itaysonlab.jetispot.core.objs.player.PlayFromContextData import bruhcollective.itaysonlab.jetispot.ui.hub.HubBinder import bruhcollective.itaysonlab.jetispot.ui.hub.HubScreenDelegate import bruhcollective.itaysonlab.jetispot.ui.hub.LocalHubScreenDelegate -import bruhcollective.itaysonlab.jetispot.ui.navigation.LocalNavigationController import bruhcollective.itaysonlab.jetispot.ui.shared.PagingErrorPage import bruhcollective.itaysonlab.jetispot.ui.shared.PagingLoadingPage import dagger.hilt.android.lifecycle.HiltViewModel @@ -37,8 +39,6 @@ fun HubScreen( ) { val scope = rememberCoroutineScope() - val navController = LocalNavigationController.current - viewModel.needContentPadding = needContentPadding LaunchedEffect(Unit) { diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/PlaylistScreen.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/PlaylistScreen.kt index 98ef880..bdc13e9 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/PlaylistScreen.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/PlaylistScreen.kt @@ -9,6 +9,7 @@ import bruhcollective.itaysonlab.jetispot.core.SpPlayerServiceManager import bruhcollective.itaysonlab.jetispot.core.SpSessionManager import bruhcollective.itaysonlab.jetispot.core.api.SpInternalApi import bruhcollective.itaysonlab.jetispot.core.api.SpPartnersApi +import bruhcollective.itaysonlab.jetispot.core.collection.SpCollectionManager import bruhcollective.itaysonlab.jetispot.core.objs.player.PlayFromContextData import bruhcollective.itaysonlab.jetispot.ui.hub.virt.PlaylistEntityView import dagger.hilt.android.lifecycle.HiltViewModel @@ -39,7 +40,8 @@ class PlaylistViewModel @Inject constructor( private val spInternalApi: SpInternalApi, private val spPartnersApi: SpPartnersApi, private val spPlayerServiceManager: SpPlayerServiceManager, - private val spMetadataRequester: SpMetadataRequester + private val spMetadataRequester: SpMetadataRequester, + private val spCollectionManager: SpCollectionManager ) : AbsHubViewModel() { val title = mutableStateOf("") @@ -48,7 +50,7 @@ class PlaylistViewModel @Inject constructor( suspend fun loadPlaylist(id: String) = load { loadPlaylistInternal(id) } suspend fun reloadPlaylist(id: String) = reload { loadPlaylistInternal(id) } - suspend fun loadPlaylistInternal(id: String) = PlaylistEntityView.getPlaylistView(id, spSessionManager, spInternalApi, spMetadataRequester).also { _playlistMetadata.value = it; title.value = it.playlist.attributes.name; }.hubResponse + private suspend fun loadPlaylistInternal(id: String) = PlaylistEntityView.getPlaylistView(id, spSessionManager, spInternalApi, spMetadataRequester, spCollectionManager).also { _playlistMetadata.value = it; title.value = it.playlist.attributes.name; }.hubResponse override fun play(data: PlayFromContextData) = play(spPlayerServiceManager, data) override suspend fun calculateDominantColor(url: String, dark: Boolean) = calculateDominantColor(spPartnersApi, url, dark) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YlDelegate.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YlDelegate.kt new file mode 100644 index 0000000..ef84634 --- /dev/null +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YlDelegate.kt @@ -0,0 +1,11 @@ +package bruhcollective.itaysonlab.jetispot.ui.screens.yourlibrary2 + +import androidx.compose.runtime.Stable +import androidx.compose.runtime.staticCompositionLocalOf + +@Stable +interface YlDelegate { + suspend fun getDisplayName(ownerUsername: String): String +} + +val LocalYlDelegate = staticCompositionLocalOf { error("YlDelegate should be initialized") } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YlRenderer.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YlRenderer.kt index c8f9795..c74e1c0 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YlRenderer.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YlRenderer.kt @@ -15,6 +15,11 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme 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.draw.clip @@ -121,12 +126,19 @@ fun YLRRootlist( modifier: Modifier ) { if (item.name.isEmpty()) return + val localYlDelegate = LocalYlDelegate.current + var playlistOwner by remember { mutableStateOf(item.ownerUsername) } + + LaunchedEffect(Unit) { + playlistOwner = localYlDelegate.getDisplayName(item.ownerUsername) + } + YLRGenericItem( picUrl = item.picture, picCircle = false, picPlaceholder = "playlist", title = item.name, - subtitle = item.ownerUsername, + subtitle = playlistOwner, modifier = modifier ) } diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YourLibraryContainerScreen.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YourLibraryContainerScreen.kt index da0a53f..89dc3e2 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YourLibraryContainerScreen.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YourLibraryContainerScreen.kt @@ -2,7 +2,13 @@ package bruhcollective.itaysonlab.jetispot.ui.screens.yourlibrary2 import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.WindowInsets +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.LazyRow import androidx.compose.foundation.lazy.items @@ -11,18 +17,31 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.AccountCircle import androidx.compose.material.icons.rounded.Check import androidx.compose.material.icons.rounded.Search -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FilterChip +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +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.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.ViewModel import bruhcollective.itaysonlab.jetispot.R -import bruhcollective.itaysonlab.jetispot.core.api.SpInternalApi +import bruhcollective.itaysonlab.jetispot.core.SpMetadataRequester import bruhcollective.itaysonlab.jetispot.core.collection.db.LocalCollectionDao import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.CollectionEntry import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.PredefCeType +import bruhcollective.itaysonlab.jetispot.core.user import bruhcollective.itaysonlab.jetispot.ui.navigation.LocalNavigationController import bruhcollective.itaysonlab.jetispot.ui.shared.PagingLoadingPage import dagger.hilt.android.lifecycle.HiltViewModel @@ -93,11 +112,13 @@ fun YourLibraryContainerScreen( viewModel.content, key = { it.javaClass.simpleName + "_" + it.ceId() }, contentType = { it.javaClass.simpleName }) { item -> - YlRenderer(item, modifier = Modifier - .clickable { navController.navigate(item.ceUri()) } - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 8.dp) - .animateItemPlacement()) + CompositionLocalProvider(LocalYlDelegate provides viewModel) { + YlRenderer(item, modifier = Modifier + .clickable { navController.navigate(item.ceUri()) } + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp) + .animateItemPlacement()) + } } } } else { @@ -141,11 +162,16 @@ fun AnimatedChipRow( @HiltViewModel class YourLibraryContainerViewModel @Inject constructor( - private val dao: LocalCollectionDao -) : ViewModel() { + private val dao: LocalCollectionDao, + private val spMetadataRequester: SpMetadataRequester +) : ViewModel(), YlDelegate { var selectedTabId: String by mutableStateOf("") var content by mutableStateOf>(emptyList()) + override suspend fun getDisplayName(ownerUsername: String) = spMetadataRequester.request { + user("spotify:user:$ownerUsername") + }.userProfiles["spotify:user:$ownerUsername"]?.name?.value ?: ownerUsername + suspend fun load() { val type = when (selectedTabId) { "playlists" -> FetchType.Playlists diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c2ff85b..7797eda 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -128,6 +128,8 @@ Actions Clear application cache Clear content metadata + Clear rootlist (playlists) + Clear database An error occurred while loading the page. Copy details From 3b3fce65582429447616580b6631814dccc27510 Mon Sep 17 00:00:00 2001 From: Outlet7493 <122801553+Outlet7493@users.noreply.github.com> Date: Wed, 17 Jan 2024 21:10:50 +0000 Subject: [PATCH 08/17] flip equality sign on updaterootlist image --- .../jetispot/core/collection/db/LocalCollectionDao.kt | 2 +- .../itaysonlab/jetispot/ui/screens/dac/DacRendererScreen.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/collection/db/LocalCollectionDao.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/collection/db/LocalCollectionDao.kt index 2b11805..984e98c 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/collection/db/LocalCollectionDao.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/collection/db/LocalCollectionDao.kt @@ -47,7 +47,7 @@ interface LocalCollectionDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun addEpisodes(vararg items: CollectionEpisode) - @Query("UPDATE rootlist SET picture = :picture WHERE uri = :uri AND CASE WHEN :overwrite THEN 1 ELSE picture <> '' END") + @Query("UPDATE rootlist SET picture = :picture WHERE uri = :uri AND CASE WHEN :overwrite THEN 1 ELSE picture == '' END") suspend fun updateRootlistPicture(uri: String, picture: String, overwrite: Boolean) @Query("SELECT * from lcTypes WHERE type = :of") diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/dac/DacRendererScreen.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/dac/DacRendererScreen.kt index f697d0e..0080445 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/dac/DacRendererScreen.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/dac/DacRendererScreen.kt @@ -177,7 +177,7 @@ class DacViewModel @Inject constructor( load(loader) } - // overwrite will update the image URI in db only if there isn't already one + // overwrite will update picture regardless if it already exists (e.g. in the case we get a bigger picture, store that instead) override suspend fun updateRootlistImage( uri: String, image: String, From 4c6a532034f18c2e552f34465be497bea01ca3fb Mon Sep 17 00:00:00 2001 From: Outlet7493 <122801553+Outlet7493@users.noreply.github.com> Date: Wed, 17 Jan 2024 21:53:03 +0000 Subject: [PATCH 09/17] match library header to home screen header with profile picture --- .../ToolbarComponent2Binder.kt | 1 - .../YourLibraryContainerScreen.kt | 46 +++++++++++++------ app/src/main/res/values/strings.xml | 2 +- 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_home/ToolbarComponent2Binder.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_home/ToolbarComponent2Binder.kt index 31136b9..83440e8 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_home/ToolbarComponent2Binder.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_home/ToolbarComponent2Binder.kt @@ -19,7 +19,6 @@ import androidx.compose.ui.unit.dp import bruhcollective.itaysonlab.jetispot.ui.ext.dynamicUnpack import bruhcollective.itaysonlab.jetispot.ui.navigation.LocalNavigationController import bruhcollective.itaysonlab.jetispot.ui.shared.PreviewableAsyncImage -import com.spotify.home.dac.component.v1.proto.ToolbarComponent import com.spotify.home.dac.component.v1.proto.ToolbarItemFeedComponent import com.spotify.home.dac.component.v1.proto.ToolbarItemListeningHistoryComponent import com.spotify.home.dac.component.v1.proto.ToolbarItemSettingsComponent diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YourLibraryContainerScreen.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YourLibraryContainerScreen.kt index 89dc3e2..dc250a9 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YourLibraryContainerScreen.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YourLibraryContainerScreen.kt @@ -9,14 +9,14 @@ import androidx.compose.foundation.layout.WindowInsets 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.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.AccountCircle import androidx.compose.material.icons.rounded.Check -import androidx.compose.material.icons.rounded.Search import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FilterChip import androidx.compose.material3.Icon @@ -32,18 +32,22 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.ViewModel import bruhcollective.itaysonlab.jetispot.R import bruhcollective.itaysonlab.jetispot.core.SpMetadataRequester +import bruhcollective.itaysonlab.jetispot.core.SpSessionManager import bruhcollective.itaysonlab.jetispot.core.collection.db.LocalCollectionDao import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.CollectionEntry import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.PredefCeType import bruhcollective.itaysonlab.jetispot.core.user import bruhcollective.itaysonlab.jetispot.ui.navigation.LocalNavigationController import bruhcollective.itaysonlab.jetispot.ui.shared.PagingLoadingPage +import bruhcollective.itaysonlab.jetispot.ui.shared.PreviewableAsyncImage import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -69,17 +73,23 @@ fun YourLibraryContainerScreen( Scaffold(topBar = { Column { - TopAppBar(title = { - Text(stringResource(id = R.string.your_library)) - }, navigationIcon = { - IconButton(onClick = { /* TODO */ }) { - Icon(Icons.Rounded.AccountCircle, null) - } - }, actions = { - IconButton(onClick = { /* TODO */ }) { - Icon(Icons.Rounded.Search, null) + TopAppBar( + title = { + Text(stringResource(id = R.string.your_library), fontWeight = FontWeight.SemiBold) + }, + navigationIcon = { + IconButton( + onClick = { navController.navigate("spotify:config") }, + modifier = Modifier.padding(start = 8.dp, end = 8.dp) + ) { + PreviewableAsyncImage(imageUrl = viewModel.profilePicture, placeholderType = "user", modifier = Modifier.size(36.dp).clip(CircleShape)) + } + }, actions = { +// IconButton(onClick = { /* TODO */ }) { +// Icon(Icons.Rounded.Search, null) +// } } - }) + ) AnimatedChipRow( listOf( @@ -163,16 +173,19 @@ fun AnimatedChipRow( @HiltViewModel class YourLibraryContainerViewModel @Inject constructor( private val dao: LocalCollectionDao, - private val spMetadataRequester: SpMetadataRequester + private val spMetadataRequester: SpMetadataRequester, + private val spSessionManager: SpSessionManager ) : ViewModel(), YlDelegate { var selectedTabId: String by mutableStateOf("") var content by mutableStateOf>(emptyList()) + var profilePicture by mutableStateOf("") override suspend fun getDisplayName(ownerUsername: String) = spMetadataRequester.request { user("spotify:user:$ownerUsername") }.userProfiles["spotify:user:$ownerUsername"]?.name?.value ?: ownerUsername suspend fun load() { + getProfilePicture() val type = when (selectedTabId) { "playlists" -> FetchType.Playlists "albums" -> FetchType.Albums @@ -215,6 +228,13 @@ class YourLibraryContainerViewModel @Inject constructor( }) } + private suspend fun getProfilePicture() { + val u = "spotify:user:${spSessionManager.session.username()}" + profilePicture = spMetadataRequester.request { + user(u) + }.userProfiles[u]?.imagesList?.firstOrNull()?.url ?: "" + } + enum class FetchType { All, Playlists, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7797eda..91781e5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -158,7 +158,7 @@ Unknown Title Unknown Artist Unknown Album - What would you like to listen? + What would you like to listen to? "Song • " Playlist • Personalized for you Playlist • By Spotify From 9d640421d827d017a10f0f6137cdea28d0afc535 Mon Sep 17 00:00:00 2001 From: Outlet7493 <122801553+Outlet7493@users.noreply.github.com> Date: Fri, 19 Jan 2024 19:42:26 +0000 Subject: [PATCH 10/17] impl search feature for your library --- .../YourLibraryContainerScreen.kt | 218 ++++++++++++++++-- app/src/main/res/values/strings.xml | 1 + 2 files changed, 198 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YourLibraryContainerScreen.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YourLibraryContainerScreen.kt index dc250a9..7808d75 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YourLibraryContainerScreen.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YourLibraryContainerScreen.kt @@ -1,8 +1,17 @@ package bruhcollective.itaysonlab.jetispot.ui.screens.yourlibrary2 +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.SizeTransform +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideOutHorizontally +import androidx.compose.animation.togetherWith import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets @@ -15,41 +24,69 @@ import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.ArrowBack import androidx.compose.material.icons.rounded.Check +import androidx.compose.material.icons.rounded.Close +import androidx.compose.material.icons.rounded.Search import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FilterChip import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar +import androidx.compose.material3.surfaceColorAtElevation 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.draw.clip +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import bruhcollective.itaysonlab.jetispot.R import bruhcollective.itaysonlab.jetispot.core.SpMetadataRequester import bruhcollective.itaysonlab.jetispot.core.SpSessionManager import bruhcollective.itaysonlab.jetispot.core.collection.db.LocalCollectionDao +import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.CollectionAlbum +import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.CollectionArtist import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.CollectionEntry +import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.CollectionEpisode +import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.CollectionPinnedItem +import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.CollectionShow import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.PredefCeType +import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.rootlist.CollectionRootlistItem import bruhcollective.itaysonlab.jetispot.core.user import bruhcollective.itaysonlab.jetispot.ui.navigation.LocalNavigationController import bruhcollective.itaysonlab.jetispot.ui.shared.PagingLoadingPage import bruhcollective.itaysonlab.jetispot.ui.shared.PreviewableAsyncImage import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.launch import javax.inject.Inject @@ -62,9 +99,13 @@ fun YourLibraryContainerScreen( viewModel: YourLibraryContainerViewModel = hiltViewModel() ) { val navController = LocalNavigationController.current + val focusManager = LocalFocusManager.current val scope = rememberCoroutineScope() val state = rememberLazyListState() + var search by remember { mutableStateOf(false) } + var query by remember { mutableStateOf("") } + LaunchedEffect(Unit) { launch { viewModel.load() @@ -75,19 +116,104 @@ fun YourLibraryContainerScreen( Column { TopAppBar( title = { - Text(stringResource(id = R.string.your_library), fontWeight = FontWeight.SemiBold) + val containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(8.dp) + AnimatedContent( + search, + transitionSpec = { + if (targetState) { + (slideInHorizontally { it } + fadeIn()).togetherWith(slideOutHorizontally { -it } + fadeOut()) + } else { + (slideInHorizontally { -it } + fadeIn()).togetherWith(slideOutHorizontally { it } + fadeOut()) + }.using(SizeTransform(clip = false)) + }, + label = "Your library search bar slide" + ) { + if (it) { + BasicTextField( + value = query, + onValueChange = { + query = it + viewModel.filter(it) + }, + modifier = Modifier.fillMaxWidth(), + singleLine = true, + textStyle = TextStyle( + fontSize = 14.sp, + color = MaterialTheme.colorScheme.onSurface + ), + cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurface), + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Search + ), + keyboardActions = KeyboardActions { + focusManager.clearFocus() + } + ) { inner -> + OutlinedTextFieldDefaults.DecorationBox( + value = query, + innerTextField = inner, + enabled = true, + singleLine = true, + visualTransformation = VisualTransformation.None, + interactionSource = remember { MutableInteractionSource() }, + trailingIcon = { + if (query.isNotEmpty()) { + IconButton(onClick = { query = "" }) { + Icon(imageVector = Icons.Rounded.Close, contentDescription = null) + } + } + }, + placeholder = { + Text(stringResource(R.string.search_your_library)) + }, + colors = OutlinedTextFieldDefaults.colors( + focusedContainerColor = containerColor, + unfocusedContainerColor = containerColor, + disabledContainerColor = containerColor, + unfocusedBorderColor = MaterialTheme.colorScheme.surfaceVariant, + ), + contentPadding = OutlinedTextFieldDefaults.contentPadding() + ) + } + } else { + Text(stringResource(id = R.string.your_library), fontWeight = FontWeight.SemiBold) + } + } }, navigationIcon = { - IconButton( - onClick = { navController.navigate("spotify:config") }, - modifier = Modifier.padding(start = 8.dp, end = 8.dp) + AnimatedContent( + search, + transitionSpec = { + if (targetState) { + (slideInHorizontally { it } + fadeIn()).togetherWith(slideOutHorizontally { -it } + fadeOut()) + } else { + (slideInHorizontally { -it } + fadeIn()).togetherWith(slideOutHorizontally { it } + fadeOut()) + }.using(SizeTransform(clip = false)) + }, + label = "Your library nav icon slide" ) { - PreviewableAsyncImage(imageUrl = viewModel.profilePicture, placeholderType = "user", modifier = Modifier.size(36.dp).clip(CircleShape)) + if (it) { + IconButton(onClick = { search = false }) { + Icon(Icons.Rounded.ArrowBack, null) + } + } else { + IconButton( + onClick = { navController.navigate("spotify:config") }, + modifier = Modifier.padding(start = 8.dp, end = 8.dp) + ) { + PreviewableAsyncImage(imageUrl = viewModel.profilePicture, placeholderType = "user", modifier = Modifier + .size(36.dp) + .clip(CircleShape)) + } + } + } + }, + actions = { + if (!search) { + IconButton(onClick = { search = true }) { + Icon(Icons.Rounded.Search, null) + } } - }, actions = { -// IconButton(onClick = { /* TODO */ }) { -// Icon(Icons.Rounded.Search, null) -// } } ) @@ -111,23 +237,37 @@ fun YourLibraryContainerScreen( } } }, contentWindowInsets = WindowInsets(bottom = 0.dp)) { padding -> - if (viewModel.content.isNotEmpty()) { + if (viewModel.loaded) { LazyColumn( state = state, modifier = Modifier .padding(padding) .fillMaxSize() ) { - items( - viewModel.content, - key = { it.javaClass.simpleName + "_" + it.ceId() }, - contentType = { it.javaClass.simpleName }) { item -> - CompositionLocalProvider(LocalYlDelegate provides viewModel) { - YlRenderer(item, modifier = Modifier - .clickable { navController.navigate(item.ceUri()) } - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 8.dp) - .animateItemPlacement()) + if (viewModel.filteredContent.isNotEmpty()) { + items( + viewModel.filteredContent, + key = { it.javaClass.simpleName + "_" + it.ceId() }, + contentType = { it.javaClass.simpleName }) { item -> + CompositionLocalProvider(LocalYlDelegate provides viewModel) { + YlRenderer(item, modifier = Modifier + .clickable { navController.navigate(item.ceUri()) } + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp) + .animateItemPlacement()) + } + } + } else { + item("NoSearchResult") { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp) + .animateItemPlacement(), + contentAlignment = Alignment.Center + ) { + Text(stringResource(R.string.search_no_results)) + } } } } @@ -170,16 +310,46 @@ fun AnimatedChipRow( } } +@OptIn(FlowPreview::class) @HiltViewModel class YourLibraryContainerViewModel @Inject constructor( private val dao: LocalCollectionDao, private val spMetadataRequester: SpMetadataRequester, private val spSessionManager: SpSessionManager ) : ViewModel(), YlDelegate { + private var content by mutableStateOf>(emptyList()) + private val debouncedSearch = MutableStateFlow("") + var loaded by mutableStateOf(false) var selectedTabId: String by mutableStateOf("") - var content by mutableStateOf>(emptyList()) + var filteredContent by mutableStateOf>(emptyList()) var profilePicture by mutableStateOf("") + init { + viewModelScope.launch { + debouncedSearch + .debounce(200L) + .collectLatest { search -> + if (content.isNotEmpty()) { + filteredContent = if (search.isEmpty()) { + content + } else { + content.filter { ce -> + when (ce) { + is CollectionRootlistItem -> ce.name.contains(search, ignoreCase = true) + is CollectionArtist -> ce.name.contains(search, ignoreCase = true) + is CollectionPinnedItem -> ce.name.contains(search, ignoreCase = true) + is CollectionEpisode -> ce.name.contains(search, ignoreCase = true) || ce.showName.contains(search, ignoreCase = true) + is CollectionAlbum -> ce.name.contains(search, ignoreCase = true) || ce.rawArtistsData.contains(search, ignoreCase = true) + is CollectionShow -> ce.name.contains(search, ignoreCase = true) || ce.publisher.contains(search, ignoreCase = true) + else -> ce.ceId().contains(search, ignoreCase = true) + } + } + } + } + } + } + } + override suspend fun getDisplayName(ownerUsername: String) = spMetadataRequester.request { user("spotify:user:$ownerUsername") }.userProfiles["spotify:user:$ownerUsername"]?.name?.value ?: ownerUsername @@ -226,6 +396,12 @@ class YourLibraryContainerViewModel @Inject constructor( } } }) + filteredContent = content + loaded = true + } + + fun filter(search: String) { + debouncedSearch.value = search } private suspend fun getProfilePicture() { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 91781e5..1e4ea3d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -159,6 +159,7 @@ Unknown Artist Unknown Album What would you like to listen to? + Search your library "Song • " Playlist • Personalized for you Playlist • By Spotify From 6bfd0a3229d3b8fdca6dfaba84e772d9e6f10803 Mon Sep 17 00:00:00 2001 From: Outlet7493 <122801553+Outlet7493@users.noreply.github.com> Date: Sat, 20 Jan 2024 14:52:25 +0000 Subject: [PATCH 11/17] add your episodes; add rootlist impl when pinned; promocardbinder init --- app/build.gradle.kts | 1 + .../core/collection/SpCollectionManager.kt | 6 ++ .../collection/db/model2/CollectionEntry.kt | 2 +- .../playback/service/SpPlaybackService.kt | 4 +- .../playback/service/refl/SpReflect.kt | 1 - .../itaysonlab/jetispot/ui/dac/DacRender.kt | 5 +- .../components_home/MediumActionCardBinder.kt | 17 +++- .../ui/dac/components_home/PromoCardBinder.kt | 89 +++++++++++++++++++ .../ui/hub/virt/PlaylistEntityView.kt | 16 ++-- .../ui/screens/dynamic/DynamicSpIdScreen.kt | 11 ++- .../ui/screens/hub/YourEpisodesScreen.kt | 13 +++ .../ui/screens/yourlibrary2/YlDelegate.kt | 1 + .../ui/screens/yourlibrary2/YlRenderer.kt | 68 ++++++++++---- .../YourLibraryContainerScreen.kt | 12 ++- .../jetispot/ui/shared/NavController.kt | 4 +- app/src/main/res/values/strings.xml | 2 + 16 files changed, 218 insertions(+), 34 deletions(-) create mode 100644 app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_home/PromoCardBinder.kt create mode 100644 app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/YourEpisodesScreen.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1aafeba..c06e490 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -62,6 +62,7 @@ android { }.toString() testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunnerArguments["disableAnalytics"] = "true" kapt { correctErrorTypes = true diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/collection/SpCollectionManager.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/collection/SpCollectionManager.kt index 348e1b0..f4ca9aa 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/collection/SpCollectionManager.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/collection/SpCollectionManager.kt @@ -95,6 +95,12 @@ class SpCollectionManager @Inject constructor( uri: String ) = dao.getRootlistImage(uri) + suspend fun updateRootlistImage( + uri: String, + image: String, + overwrite: Boolean + ) = dao.updateRootlistPicture(uri, image, overwrite) + suspend fun clearRootlist() { dao.deleteRootList() dao.deleteCollectionCategory("rootlist") diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/collection/db/model2/CollectionEntry.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/collection/db/model2/CollectionEntry.kt index f7c035e..0acbc97 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/collection/db/model2/CollectionEntry.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/collection/db/model2/CollectionEntry.kt @@ -8,5 +8,5 @@ interface CollectionEntry { } enum class PredefCeType { - COLLECTION, EPISODES + COLLECTION, EPISODES, YOUR_EPISODES, ROOTLIST } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/playback/service/SpPlaybackService.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/playback/service/SpPlaybackService.kt index 2b74f45..31d41e7 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/playback/service/SpPlaybackService.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/playback/service/SpPlaybackService.kt @@ -4,9 +4,7 @@ import android.annotation.SuppressLint import android.app.PendingIntent import android.content.Intent import android.net.Uri -import android.os.Build import android.os.Bundle -import androidx.media2.session.LibraryResult import androidx.media2.session.MediaLibraryService import androidx.media2.session.MediaSession import androidx.media2.session.SessionResult @@ -72,7 +70,7 @@ class SpPlaybackService : MediaLibraryService(), CoroutineScope by CoroutineScop Intent(this@SpPlaybackService, MainActivity::class.java).apply { putExtra("openPlayer", true) }, - (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0) or PendingIntent.FLAG_UPDATE_CURRENT + (PendingIntent.FLAG_IMMUTABLE) or PendingIntent.FLAG_UPDATE_CURRENT ) ) }.build() diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/playback/service/refl/SpReflect.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/playback/service/refl/SpReflect.kt index 838691e..26b62b3 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/playback/service/refl/SpReflect.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/playback/service/refl/SpReflect.kt @@ -2,7 +2,6 @@ package bruhcollective.itaysonlab.jetispot.playback.service.refl import androidx.media2.common.SessionPlayer import com.google.gson.JsonParser -import xyz.gianlu.librespot.player.PagesLoader import xyz.gianlu.librespot.player.Player class SpReflect( diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/DacRender.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/DacRender.kt index fdad1bc..81455b3 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/DacRender.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/DacRender.kt @@ -10,6 +10,7 @@ import androidx.compose.ui.unit.dp import bruhcollective.itaysonlab.jetispot.BuildConfig import bruhcollective.itaysonlab.jetispot.proto.ErrorComponent import bruhcollective.itaysonlab.jetispot.ui.dac.components_home.MediumActionCardBinder +import bruhcollective.itaysonlab.jetispot.ui.dac.components_home.PromoCardBinder import bruhcollective.itaysonlab.jetispot.ui.dac.components_home.RecentlyPlayedSectionComponentBinder import bruhcollective.itaysonlab.jetispot.ui.dac.components_home.RecsplanationHeadingComponentBinder import bruhcollective.itaysonlab.jetispot.ui.dac.components_home.RecsplanationHeadingSingleTextComponentBinder @@ -35,6 +36,7 @@ import com.spotify.home.dac.component.v1.proto.ArtistCardActionsMediumComponent import com.spotify.home.dac.component.v1.proto.ArtistCardActionsSmallComponent import com.spotify.home.dac.component.v1.proto.PlaylistCardActionsMediumComponent import com.spotify.home.dac.component.v1.proto.PlaylistCardActionsSmallComponent +import com.spotify.home.dac.component.v1.proto.PromoCardHomeComponent import com.spotify.home.dac.component.v1.proto.RecentlyPlayedSectionComponent import com.spotify.home.dac.component.v1.proto.RecsplanationHeadingComponent import com.spotify.home.dac.component.v1.proto.SectionComponent @@ -55,7 +57,6 @@ fun DacRender( item: Message ) { when (item) { - // AllPlans / PlanOverview is MultiUserMemberComponent -> MultiUserMemberComponentBinder(item) is BenefitListComponent -> BenefitListComponentBinder(item) @@ -85,6 +86,8 @@ fun DacRender( is SectionComponent -> SectionComponentBinder(item) is RecentlyPlayedSectionComponent -> RecentlyPlayedSectionComponentBinder() + is PromoCardHomeComponent -> PromoCardBinder(item) + //Podcasts //EpisodeCardActionsMediumComponent -> diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_home/MediumActionCardBinder.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_home/MediumActionCardBinder.kt index a68a007..7d55d7f 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_home/MediumActionCardBinder.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_home/MediumActionCardBinder.kt @@ -1,11 +1,24 @@ package bruhcollective.itaysonlab.jetispot.ui.dac.components_home import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +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.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.* +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.unit.dp diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_home/PromoCardBinder.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_home/PromoCardBinder.kt new file mode 100644 index 0000000..d011ddd --- /dev/null +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_home/PromoCardBinder.kt @@ -0,0 +1,89 @@ +package bruhcollective.itaysonlab.jetispot.ui.dac.components_home + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +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.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.MaterialTheme +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.unit.dp +import bruhcollective.itaysonlab.jetispot.ui.ext.compositeSurfaceElevation +import bruhcollective.itaysonlab.jetispot.ui.monet.ColorToScheme +import bruhcollective.itaysonlab.jetispot.ui.shared.MediumText +import bruhcollective.itaysonlab.jetispot.ui.shared.PreviewableAsyncImage +import bruhcollective.itaysonlab.jetispot.ui.shared.Subtext +import bruhcollective.itaysonlab.jetispot.ui.shared.dynamic_blocks.DynamicPlayButton +import bruhcollective.itaysonlab.jetispot.ui.shared.navClickable +import com.spotify.home.dac.component.v1.proto.PromoCardHomeComponent + +@Composable +fun PromoCardBinder( + item: PromoCardHomeComponent +) { + val imagePlaceholder = item.navigateUri.split(":").getOrNull(1) ?: "playlist" + + val curScheme = MaterialTheme.colorScheme + val isDark = isSystemInDarkTheme() + var colorScheme by remember { mutableStateOf(curScheme) } + + LaunchedEffect(item.gradientColor) { + val clr = android.graphics.Color.parseColor("#${item.gradientColor}") + colorScheme = ColorToScheme.convert(clr, isDark) + } + + MaterialTheme(colorScheme = colorScheme) { + Card(colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.compositeSurfaceElevation( + 3.dp + ) + ), modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth() + .navClickable { navController -> + navController.navigate(item.navigateUri) + }) { + Column(Modifier.padding(16.dp)) { + Row { + PreviewableAsyncImage( + imageUrl = item.logoImageUri, placeholderType = imagePlaceholder, modifier = Modifier + .fillMaxHeight() + .size(140.dp) + ) + + Spacer(modifier = Modifier.width(8.dp)) + + Column { + MediumText(text = item.title, maxLines = 2) + Spacer(modifier = Modifier.height(8.dp)) + Subtext(text = item.subtitle) + } + } + + Spacer(modifier = Modifier.height(8.dp)) + + Row(verticalAlignment = Alignment.CenterVertically) { + Spacer(Modifier.weight(1f)) + DynamicPlayButton( + command = item.playCommand, + Modifier.size(42.dp) + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/virt/PlaylistEntityView.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/virt/PlaylistEntityView.kt index b7d1e80..599d7e7 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/virt/PlaylistEntityView.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/virt/PlaylistEntityView.kt @@ -49,6 +49,16 @@ object PlaylistEntityView { tracks(playlistTracks.map { it.uri }) } + var imageUri = playlist.attributes.formatAttributesList.find { it.key == "image" }?.value + ?: playlist.attributes.formatAttributesList.find { it.key == "image_url" }?.value + ?: playlist.attributes.pictureSizeList.find { it.targetName == "default" }?.url + ?: if (playlist.attributes.hasPicture()) "https://i.scdn.co/image/${Utils.bytesToHex(playlist.attributes.picture).lowercase()}" else "" + if (imageUri.isEmpty()) { + imageUri = spCollectionManager.getRootlistImage("spotify:playlist:$id") ?: "" + } else { + spCollectionManager.updateRootlistImage("spotify:playlist:$id", imageUri, overwrite = true) + } + val mappedDuration = mappedMetadata.tracks.map { it.value.duration / 1000L }.sum() val playlistOwner = mappedMetadata.userProfiles[playlistOwnerUsername]!! val popCount = spInternalApi.getPlaylistPopCount(id) @@ -88,11 +98,7 @@ object PlaylistEntityView { ), images = HubImages( HubImage( - uri = playlist.attributes.formatAttributesList.find { it.key == "image" }?.value - ?: playlist.attributes.formatAttributesList.find { it.key == "image_url" }?.value - ?: playlist.attributes.pictureSizeList.find { it.targetName == "default" }?.url - ?: if (playlist.attributes.hasPicture()) "https://i.scdn.co/image/${Utils.bytesToHex(playlist.attributes.picture).lowercase()}" - else spCollectionManager.getRootlistImage("spotify:playlist:$id") ?: "" + uri = imageUri ) ) ) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/dynamic/DynamicSpIdScreen.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/dynamic/DynamicSpIdScreen.kt index 8c9644a..172c37d 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/dynamic/DynamicSpIdScreen.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/dynamic/DynamicSpIdScreen.kt @@ -10,7 +10,15 @@ import androidx.compose.ui.Modifier import bruhcollective.itaysonlab.jetispot.ui.screens.blend.BlendCreateInvitationScreen import bruhcollective.itaysonlab.jetispot.ui.screens.config.ConfigScreen import bruhcollective.itaysonlab.jetispot.ui.screens.history.ListeningHistoryScreen -import bruhcollective.itaysonlab.jetispot.ui.screens.hub.* +import bruhcollective.itaysonlab.jetispot.ui.screens.hub.AlbumScreen +import bruhcollective.itaysonlab.jetispot.ui.screens.hub.BrowseRadioScreen +import bruhcollective.itaysonlab.jetispot.ui.screens.hub.BrowseScreen +import bruhcollective.itaysonlab.jetispot.ui.screens.hub.CollectionScreen +import bruhcollective.itaysonlab.jetispot.ui.screens.hub.HubScreen +import bruhcollective.itaysonlab.jetispot.ui.screens.hub.LikedSongsScreen +import bruhcollective.itaysonlab.jetispot.ui.screens.hub.PlaylistScreen +import bruhcollective.itaysonlab.jetispot.ui.screens.hub.PodcastShowScreen +import bruhcollective.itaysonlab.jetispot.ui.screens.hub.YourEpisodesScreen @Composable fun DynamicSpIdScreen( @@ -47,6 +55,7 @@ fun DynamicSpIdScreen( id = argument, fullUri = fullUri ) + "your-episodes" -> YourEpisodesScreen() "" -> CollectionScreen() /* else -> { TODO } */ } diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/YourEpisodesScreen.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/YourEpisodesScreen.kt new file mode 100644 index 0000000..3039ebc --- /dev/null +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/YourEpisodesScreen.kt @@ -0,0 +1,13 @@ +package bruhcollective.itaysonlab.jetispot.ui.screens.hub + +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import bruhcollective.itaysonlab.jetispot.R + +@Composable +fun YourEpisodesScreen( + +) { + Text(stringResource(R.string.your_episodes)) +} \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YlDelegate.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YlDelegate.kt index ef84634..eeab826 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YlDelegate.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YlDelegate.kt @@ -6,6 +6,7 @@ import androidx.compose.runtime.staticCompositionLocalOf @Stable interface YlDelegate { suspend fun getDisplayName(ownerUsername: String): String + suspend fun getPinnedRootlistPicture(uri: String): String? } val LocalYlDelegate = staticCompositionLocalOf { error("YlDelegate should be initialized") } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YlRenderer.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YlRenderer.kt index c74e1c0..9702ed4 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YlRenderer.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YlRenderer.kt @@ -7,8 +7,10 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Bookmark import androidx.compose.material.icons.rounded.Favorite import androidx.compose.material.icons.rounded.Photo +import androidx.compose.material.icons.rounded.PlaylistPlay import androidx.compose.material.icons.rounded.Podcasts import androidx.compose.material.icons.rounded.PushPin import androidx.compose.material3.Icon @@ -58,12 +60,29 @@ fun YLRPinned( item: CollectionPinnedItem, modifier: Modifier ) { + if (item.predefType == PredefCeType.COLLECTION && item.predefDyn.toIntOrNull() == 0) return // don't display "liked songs" if the rootlist is empty + + val ylDelegate = LocalYlDelegate.current + var ownerUsername by remember { mutableStateOf(item.subtitle) } + + // if user has pinned a playlist, fetch display name instead of uuid + LaunchedEffect(Unit) { + if (item.predefType == PredefCeType.ROOTLIST) { + ownerUsername = ylDelegate.getDisplayName(item.subtitle) + } + } + Row(modifier) { val isPredef = item.predefType != null - if (isPredef) { + if (isPredef && item.predefType != PredefCeType.ROOTLIST) { ImagePreview( - if (item.predefType == PredefCeType.COLLECTION) Icons.Rounded.Favorite else Icons.Rounded.Podcasts, + when (item.predefType) { + PredefCeType.COLLECTION -> Icons.Rounded.Favorite + PredefCeType.EPISODES -> Icons.Rounded.Podcasts + PredefCeType.YOUR_EPISODES -> Icons.Rounded.Bookmark + PredefCeType.ROOTLIST, null -> Icons.Rounded.PlaylistPlay + }, true, modifier = Modifier .size(64.dp) @@ -71,13 +90,29 @@ fun YLRPinned( ) } else { if (item.picture.isEmpty()) { - ImagePreview( - Icons.Rounded.Photo, - false, - modifier = Modifier - .size(64.dp) - .clip(RoundedCornerShape(8.dp)) - ) + var pic by remember { mutableStateOf(null) } + + LaunchedEffect(Unit) { + pic = ylDelegate.getPinnedRootlistPicture(item.ceUri()) + } + + if (pic == null) { + ImagePreview( + Icons.Rounded.Photo, + false, + modifier = Modifier + .size(64.dp) + .clip(RoundedCornerShape(8.dp)) + ) + } else { + AsyncImage( + model = pic, + contentDescription = null, + modifier = Modifier + .size(64.dp) + .clip(RoundedCornerShape(8.dp)) + ) + } } else { AsyncImage( model = "https://i.scdn.co/image/${item.picture}", @@ -94,9 +129,10 @@ fun YLRPinned( .padding(start = 16.dp) .align(Alignment.CenterVertically)) { Text(text = when (item.predefType) { - PredefCeType.COLLECTION -> stringResource(id = R.string.liked_songs) - PredefCeType.EPISODES -> stringResource(id = R.string.new_episodes) - null -> item.name + PredefCeType.COLLECTION -> stringResource(R.string.liked_songs) + PredefCeType.EPISODES -> stringResource(R.string.new_episodes) + PredefCeType.YOUR_EPISODES -> stringResource(R.string.your_episodes) + else -> item.name }, maxLines = 1, overflow = TextOverflow.Ellipsis) Row(Modifier.padding(top = 4.dp)) { Icon(Icons.Rounded.PushPin, tint = MaterialTheme.colorScheme.primary, contentDescription = null, modifier = Modifier @@ -104,9 +140,11 @@ fun YLRPinned( .align(Alignment.CenterVertically)) Text( text = when (item.predefType) { - PredefCeType.COLLECTION -> stringResource(id = R.string.liked_songs_desc, item.predefDyn) - PredefCeType.EPISODES -> stringResource(id = R.string.new_episodes_desc, item.predefDyn) - null -> item.subtitle + PredefCeType.COLLECTION -> stringResource(R.string.liked_songs_desc, item.predefDyn) + PredefCeType.EPISODES -> stringResource(R.string.new_episodes_desc, item.predefDyn) + PredefCeType.YOUR_EPISODES -> stringResource(R.string.saved_episodes) + PredefCeType.ROOTLIST -> ownerUsername + else -> item.subtitle }, color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), maxLines = 1, diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YourLibraryContainerScreen.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YourLibraryContainerScreen.kt index 7808d75..8b62226 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YourLibraryContainerScreen.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YourLibraryContainerScreen.kt @@ -131,9 +131,9 @@ fun YourLibraryContainerScreen( if (it) { BasicTextField( value = query, - onValueChange = { - query = it - viewModel.filter(it) + onValueChange = { q -> + query = q + viewModel.filter(q) }, modifier = Modifier.fillMaxWidth(), singleLine = true, @@ -354,6 +354,10 @@ class YourLibraryContainerViewModel @Inject constructor( user("spotify:user:$ownerUsername") }.userProfiles["spotify:user:$ownerUsername"]?.name?.value ?: ownerUsername + override suspend fun getPinnedRootlistPicture(uri: String): String? { + return dao.getRootlistImage(uri) + } + suspend fun load() { getProfilePicture() val type = when (selectedTabId) { @@ -393,8 +397,10 @@ class YourLibraryContainerViewModel @Inject constructor( when (pF.ceUri()) { "spotify:collection" -> pF.ceModifyPredef(PredefCeType.COLLECTION, dao.trackCount().toString()) "spotify:collection:podcasts:episodes" -> pF.ceModifyPredef(PredefCeType.EPISODES, "") + "spotify:collection:your-episodes" -> pF.ceModifyPredef(PredefCeType.YOUR_EPISODES, "") } } + it.filter { p -> p.ceUri().startsWith("spotify:playlist:") }.map { pF -> pF.ceModifyPredef(PredefCeType.ROOTLIST, "") } }) filteredContent = content loaded = true diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/shared/NavController.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/shared/NavController.kt index 4b796cf..c7c0b6f 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/shared/NavController.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/shared/NavController.kt @@ -24,7 +24,7 @@ fun Modifier.navClickable( interactionSource = remember { MutableInteractionSource() }, ) { onClick(navController) - } + }.then(this) } fun Modifier.navAndHubClickable( @@ -41,5 +41,5 @@ fun Modifier.navAndHubClickable( interactionSource = remember { MutableInteractionSource() }, ) { onClick(navController, hubScreenDelegate) - } + }.then(this) } \ 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 1e4ea3d..cf974a4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -93,8 +93,10 @@ Liked Songs New Episodes + Your Episodes %s songs Last updated on %s + Saved episodes Sort by Recently added From f616f14875ca02d428d94deb575e6504824046dd Mon Sep 17 00:00:00 2001 From: Outlet7493 <122801553+Outlet7493@users.noreply.github.com> Date: Sat, 20 Jan 2024 14:55:02 +0000 Subject: [PATCH 12/17] remove duplicates in playlist (e.g. daylist is pinned & unpinned) --- .../ui/screens/yourlibrary2/YourLibraryContainerScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YourLibraryContainerScreen.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YourLibraryContainerScreen.kt index 8b62226..0cb4a85 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YourLibraryContainerScreen.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YourLibraryContainerScreen.kt @@ -401,7 +401,7 @@ class YourLibraryContainerViewModel @Inject constructor( } } it.filter { p -> p.ceUri().startsWith("spotify:playlist:") }.map { pF -> pF.ceModifyPredef(PredefCeType.ROOTLIST, "") } - }) + }).distinctBy { it.ceUri() } filteredContent = content loaded = true } From ad579d6481af1de0328224f55151ad0c77138613 Mon Sep 17 00:00:00 2001 From: Outlet7493 <122801553+Outlet7493@users.noreply.github.com> Date: Sun, 21 Jan 2024 20:13:26 +0000 Subject: [PATCH 13/17] fix crash --- .../jetispot/core/util/PlayCommandFactory.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/PlayCommandFactory.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/PlayCommandFactory.kt index 9a21f6d..05ae4d2 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/PlayCommandFactory.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/PlayCommandFactory.kt @@ -1,9 +1,13 @@ package bruhcollective.itaysonlab.jetispot.core.util -import bruhcollective.itaysonlab.jetispot.core.objs.player.* +import bruhcollective.itaysonlab.jetispot.core.objs.player.PfcContextData +import bruhcollective.itaysonlab.jetispot.core.objs.player.PfcOptSkipTo +import bruhcollective.itaysonlab.jetispot.core.objs.player.PfcOptions +import bruhcollective.itaysonlab.jetispot.core.objs.player.PfcStateOptions +import bruhcollective.itaysonlab.jetispot.core.objs.player.PlayFromContextData +import bruhcollective.itaysonlab.jetispot.core.objs.player.PlayFromContextPlayerData import com.spotify.dac.player.v1.proto.PlayCommand import com.squareup.moshi.Moshi -import com.squareup.moshi.adapter @DslMarker @Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS) @@ -39,8 +43,9 @@ class PlayCommandBuilder ( } fun PlayCommand.toApplicationPlayCommand(moshi: Moshi): PlayFromContextData { + // TODO needs investigation when coming from e.g. 'new release from' on DAC val context = moshi.adapter(PfcContextData::class.java).fromJson(this.context.toStringUtf8())!! - val options = moshi.adapter(PlayFromContextPlayerData::class.java).fromJson(this.options.toStringUtf8())!!.options!! + val options = moshi.adapter(PlayFromContextPlayerData::class.java).fromJson(this.options.toStringUtf8())?.options return PlayFromContextData( uri = context.uri, From 453b6bfead65bbe4adc0ae1ecc2fba56a9a86e92 Mon Sep 17 00:00:00 2001 From: Outlet7493 <122801553+Outlet7493@users.noreply.github.com> Date: Sun, 21 Jan 2024 20:54:58 +0000 Subject: [PATCH 14/17] fix BobbyESP/Jetispot#9 --- .../itaysonlab/jetispot/MainActivity.kt | 6 +++-- .../jetispot/core/SpPlayerManager.kt | 2 +- .../jetispot/core/ext/ModifierExt.kt | 11 ++++++++++ .../jetispot/core/util/PlayCommandFactory.kt | 2 +- .../jetispot/playback/sp/AndroidSinkOutput.kt | 1 - .../ToolbarComponent2Binder.kt | 10 +++++++-- .../ui/screens/hub/LikedSongsScreen.kt | 15 +++++++++++-- .../nowplaying/NowPlayingMiniplayer.kt | 13 +++++++++-- .../ui/screens/nowplaying/NowPlayingScreen.kt | 3 +++ .../NowPlayingFullscreenComposition.kt | 22 ++++++++++++------- .../nowplaying/fullscreen/NowPlayingHeader.kt | 20 +++++------------ .../YourLibraryContainerScreen.kt | 12 +++++++--- 12 files changed, 81 insertions(+), 36 deletions(-) create mode 100644 app/src/main/java/bruhcollective/itaysonlab/jetispot/core/ext/ModifierExt.kt diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/MainActivity.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/MainActivity.kt index a7c3f94..e1b2ee8 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/MainActivity.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/MainActivity.kt @@ -18,6 +18,7 @@ import androidx.compose.material.rememberBottomSheetScaffoldState import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp @@ -25,7 +26,6 @@ import androidx.core.view.WindowCompat import androidx.navigation.NavController import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.compose.currentBackStackEntryAsState -import androidx.navigation.compose.rememberNavController import bruhcollective.itaysonlab.jetispot.core.SpAuthManager import bruhcollective.itaysonlab.jetispot.core.SpPlayerServiceManager import bruhcollective.itaysonlab.jetispot.core.SpSessionManager @@ -178,6 +178,7 @@ class MainActivity : ComponentActivity() { ) }, label = { Text(stringResource(screen.title)) }, + alwaysShowLabel = false, selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true, onClick = { navController.navigate(screen.route) { @@ -193,7 +194,7 @@ class MainActivity : ComponentActivity() { } } } - ) { innerPadding -> + ) { _ -> BottomSheetScaffold( sheetContent = { NowPlayingScreen( @@ -208,6 +209,7 @@ class MainActivity : ComponentActivity() { scaffoldState = bsState, sheetPeekHeight = bsPeek, backgroundColor = MaterialTheme.colorScheme.surface, + sheetBackgroundColor = Color.Transparent, sheetGesturesEnabled = !bsQueueOpened && !bsLyricsOpened ) { innerScaffoldPadding -> AppNavigation( diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/SpPlayerManager.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/SpPlayerManager.kt index f5d49d1..f3d8bb8 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/SpPlayerManager.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/SpPlayerManager.kt @@ -1,10 +1,10 @@ package bruhcollective.itaysonlab.jetispot.core import android.os.Looper -import bruhcollective.itaysonlab.jetispot.proto.AudioNormalization import bruhcollective.itaysonlab.jetispot.playback.service.refl.SpReflect import bruhcollective.itaysonlab.jetispot.playback.sp.AndroidSinkOutput import bruhcollective.itaysonlab.jetispot.playback.sp.LowToHighQualityPicker +import bruhcollective.itaysonlab.jetispot.proto.AudioNormalization import xyz.gianlu.librespot.audio.decoders.AudioQuality import xyz.gianlu.librespot.player.Player import xyz.gianlu.librespot.player.PlayerConfiguration diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/ext/ModifierExt.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/ext/ModifierExt.kt new file mode 100644 index 0000000..d0a968b --- /dev/null +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/ext/ModifierExt.kt @@ -0,0 +1,11 @@ +package bruhcollective.itaysonlab.jetispot.core.ext + +import androidx.compose.ui.Modifier + +fun Modifier.conditional(condition : Boolean, modifier : Modifier.() -> Modifier) : Modifier { + return if (condition) { + then(modifier(Modifier)) + } else { + this + } +} \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/PlayCommandFactory.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/PlayCommandFactory.kt index 05ae4d2..27c041a 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/PlayCommandFactory.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/PlayCommandFactory.kt @@ -43,7 +43,7 @@ class PlayCommandBuilder ( } fun PlayCommand.toApplicationPlayCommand(moshi: Moshi): PlayFromContextData { - // TODO needs investigation when coming from e.g. 'new release from' on DAC + // TODO playing needs investigation when coming from album i.e. 'new release from' on DAC val context = moshi.adapter(PfcContextData::class.java).fromJson(this.context.toStringUtf8())!! val options = moshi.adapter(PlayFromContextPlayerData::class.java).fromJson(this.options.toStringUtf8())?.options diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/playback/sp/AndroidSinkOutput.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/playback/sp/AndroidSinkOutput.kt index 81bd7e8..7103499 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/playback/sp/AndroidSinkOutput.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/playback/sp/AndroidSinkOutput.kt @@ -12,7 +12,6 @@ import xyz.gianlu.librespot.player.mixing.output.OutputAudioFormat import xyz.gianlu.librespot.player.mixing.output.SinkException import xyz.gianlu.librespot.player.mixing.output.SinkOutput -@RequiresApi(Build.VERSION_CODES.M) class AndroidSinkOutput: SinkOutput { private var track: AudioTrack? = null private var lastVolume = -1F diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_home/ToolbarComponent2Binder.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_home/ToolbarComponent2Binder.kt index 83440e8..0d09116 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_home/ToolbarComponent2Binder.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_home/ToolbarComponent2Binder.kt @@ -1,5 +1,6 @@ package bruhcollective.itaysonlab.jetispot.ui.dac.components_home +import androidx.compose.foundation.border import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -9,7 +10,12 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.History import androidx.compose.material.icons.rounded.Notifications import androidx.compose.material.icons.rounded.Settings -import androidx.compose.material3.* +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -44,7 +50,7 @@ fun ToolbarComponent2Binder( IconButton(onClick = { navController.navigate("spotify:config") // TODO until we implement user pages }, modifier = Modifier.padding(start = 8.dp, end = 6.dp)) { - PreviewableAsyncImage(imageUrl = item.profileButton.imageUri, placeholderType = "user", modifier = Modifier.size(36.dp).clip(CircleShape)) + PreviewableAsyncImage(imageUrl = item.profileButton.imageUri, placeholderType = "user", modifier = Modifier.size(36.dp).border(1.dp, MaterialTheme.colorScheme.onSurface, CircleShape).clip(CircleShape)) } }) } diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/LikedSongsScreen.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/LikedSongsScreen.kt index 2317395..6839b38 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/LikedSongsScreen.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/LikedSongsScreen.kt @@ -9,8 +9,19 @@ import androidx.hilt.navigation.compose.hiltViewModel import bruhcollective.itaysonlab.jetispot.R import bruhcollective.itaysonlab.jetispot.core.SpPlayerServiceManager import bruhcollective.itaysonlab.jetispot.core.collection.SpCollectionManager -import bruhcollective.itaysonlab.jetispot.core.objs.hub.* -import bruhcollective.itaysonlab.jetispot.core.objs.player.* +import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubComponent +import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubEvent +import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubEvents +import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubImage +import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubImages +import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubItem +import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubResponse +import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubText +import bruhcollective.itaysonlab.jetispot.core.objs.player.PfcContextData +import bruhcollective.itaysonlab.jetispot.core.objs.player.PfcOptSkipTo +import bruhcollective.itaysonlab.jetispot.core.objs.player.PfcOptions +import bruhcollective.itaysonlab.jetispot.core.objs.player.PlayFromContextData +import bruhcollective.itaysonlab.jetispot.core.objs.player.PlayFromContextPlayerData import dagger.hilt.android.lifecycle.HiltViewModel import xyz.gianlu.librespot.metadata.ArtistId import javax.inject.Inject diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/NowPlayingMiniplayer.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/NowPlayingMiniplayer.kt index b794a41..b3642e2 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/NowPlayingMiniplayer.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/NowPlayingMiniplayer.kt @@ -4,13 +4,22 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.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.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/NowPlayingScreen.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/NowPlayingScreen.kt index 7faa250..ba4f50b 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/NowPlayingScreen.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/NowPlayingScreen.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.BottomSheetState import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.runtime.Composable @@ -13,6 +14,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel @@ -50,6 +52,7 @@ fun NowPlayingScreen( NowPlayingMiniplayer( viewModel, Modifier + .clip(RoundedCornerShape(16.dp, 16.dp, 0.dp, 0.dp)) .alpha(1f - bsOffset()) .clickable { scope.launch { bottomSheetState.expand() } } .fillMaxWidth() diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingFullscreenComposition.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingFullscreenComposition.kt index c67bd74..2911629 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingFullscreenComposition.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingFullscreenComposition.kt @@ -6,7 +6,15 @@ import androidx.compose.animation.core.spring import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.BottomSheetState import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material3.MaterialTheme @@ -16,6 +24,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.IntOffset @@ -23,7 +32,6 @@ import androidx.compose.ui.unit.dp import bruhcollective.itaysonlab.jetispot.ui.ext.compositeSurfaceElevation import bruhcollective.itaysonlab.jetispot.ui.ext.disableTouch import bruhcollective.itaysonlab.jetispot.ui.screens.nowplaying.NowPlayingViewModel -import bruhcollective.itaysonlab.jetispot.ui.theme.ApplicationTheme import kotlinx.coroutines.launch @OptIn(ExperimentalMaterialApi::class) @@ -60,12 +68,13 @@ fun NowPlayingFullscreenComposition( } Box(modifier = Modifier .fillMaxSize() + .clip(RoundedCornerShape(16.dp, 16.dp, 0.dp, 0.dp)) .background( MaterialTheme.colorScheme.compositeSurfaceElevation( 3.dp ) - )) { - + ) + ) { NowPlayingBackground( viewModel = viewModel, modifier = Modifier @@ -92,10 +101,7 @@ fun NowPlayingFullscreenComposition( .statusBarsPadding() .align(Alignment.TopCenter) .fillMaxWidth() - .padding(horizontal = 16.dp), - viewModel = viewModel, - bottomSheetState = bottomSheetState, - scope = scope, + .padding(horizontal = 16.dp) ) } // composite diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingHeader.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingHeader.kt index 9cfc20d..cfecb7d 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingHeader.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingHeader.kt @@ -1,13 +1,14 @@ package bruhcollective.itaysonlab.jetispot.ui.screens.nowplaying.fullscreen -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.BottomSheetState +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Close import androidx.compose.material.icons.rounded.KeyboardArrowDown -import androidx.compose.material.icons.rounded.MoreVert import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Text @@ -15,18 +16,12 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha -import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.scale -import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import bruhcollective.itaysonlab.jetispot.ui.screens.nowplaying.NowPlayingViewModel -import bruhcollective.itaysonlab.jetispot.ui.shared.navClickable -import kotlinx.coroutines.CoroutineScope @OptIn(ExperimentalMaterialApi::class) @Composable @@ -35,10 +30,7 @@ fun NowPlayingHeader( state: String, queueStateProgress: Float, onCloseClick: () -> Unit, - modifier: Modifier, - viewModel: NowPlayingViewModel, - bottomSheetState: BottomSheetState, - scope: CoroutineScope + modifier: Modifier ) { Row(modifier, verticalAlignment = Alignment.CenterVertically) { IconButton(onClick = onCloseClick, Modifier.size(32.dp)) { diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YourLibraryContainerScreen.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YourLibraryContainerScreen.kt index 0cb4a85..1779482 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YourLibraryContainerScreen.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/yourlibrary2/YourLibraryContainerScreen.kt @@ -8,6 +8,7 @@ import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.togetherWith import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement @@ -201,9 +202,14 @@ fun YourLibraryContainerScreen( onClick = { navController.navigate("spotify:config") }, modifier = Modifier.padding(start = 8.dp, end = 8.dp) ) { - PreviewableAsyncImage(imageUrl = viewModel.profilePicture, placeholderType = "user", modifier = Modifier - .size(36.dp) - .clip(CircleShape)) + PreviewableAsyncImage( + imageUrl = viewModel.profilePicture, + placeholderType = "user", + modifier = Modifier + .size(36.dp) + .clip(CircleShape) + .border(1.dp, MaterialTheme.colorScheme.onSurface, CircleShape) + ) } } } From c5f58859ec8803845476de017aabeb4633585db5 Mon Sep 17 00:00:00 2001 From: Outlet7493 <122801553+Outlet7493@users.noreply.github.com> Date: Sun, 21 Jan 2024 21:17:51 +0000 Subject: [PATCH 15/17] update deprecated calls --- .../jetispot/core/ext/ModifierExt.kt | 11 -------- .../library/SessionControllerVerifier.kt | 1 + .../ui/screens/config/QualityConfigScreen.kt | 3 +-- .../jetispot/ui/screens/hub/HubExt.kt | 2 +- .../ui/screens/search/SearchScreen.kt | 26 ++++++++++++++----- 5 files changed, 23 insertions(+), 20 deletions(-) delete mode 100644 app/src/main/java/bruhcollective/itaysonlab/jetispot/core/ext/ModifierExt.kt diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/ext/ModifierExt.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/ext/ModifierExt.kt deleted file mode 100644 index d0a968b..0000000 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/ext/ModifierExt.kt +++ /dev/null @@ -1,11 +0,0 @@ -package bruhcollective.itaysonlab.jetispot.core.ext - -import androidx.compose.ui.Modifier - -fun Modifier.conditional(condition : Boolean, modifier : Modifier.() -> Modifier) : Modifier { - return if (condition) { - then(modifier(Modifier)) - } else { - this - } -} \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/playback/service/library/SessionControllerVerifier.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/playback/service/library/SessionControllerVerifier.kt index b4eee9a..9c828e2 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/playback/service/library/SessionControllerVerifier.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/playback/service/library/SessionControllerVerifier.kt @@ -149,6 +149,7 @@ class SessionControllerVerifier @Inject constructor( private fun pmNewSignaturesSupported() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P @SuppressLint("PackageManagerGetSignatures") + @Suppress("DEPRECATION") private fun pmFlags() = if (pmNewSignaturesSupported()) PackageManager.GET_SIGNING_CERTIFICATES else PackageManager.GET_SIGNATURES @RequiresApi(Build.VERSION_CODES.P) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/config/QualityConfigScreen.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/config/QualityConfigScreen.kt index de01adc..5ee5474 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/config/QualityConfigScreen.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/config/QualityConfigScreen.kt @@ -8,7 +8,6 @@ import bruhcollective.itaysonlab.jetispot.core.SpConfigurationManager import bruhcollective.itaysonlab.jetispot.core.SpSessionManager import bruhcollective.itaysonlab.jetispot.proto.AppConfig import bruhcollective.itaysonlab.jetispot.proto.AudioQuality -import com.spotify.pamviewservice.v1.proto.PremiumPlanRow import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject @@ -53,7 +52,7 @@ class QualityConfigScreenViewModel @Inject constructor( })) //if the user doesn't have premium, don't show the option to select very high quality - if (spSessionManager.session?.getUserAttribute("name") != "Spotify Free") { + if (spSessionManager.session.getUserAttribute("name") != "Spotify Free") { add(ConfigItem.Radio(R.string.quality_very_high, R.string.quality_very_high_desc, { it.playerConfig.preferredQuality == AudioQuality.VERY_HIGH }, { true }, { diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/HubExt.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/HubExt.kt index d5864e2..ba4b8c4 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/HubExt.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/HubExt.kt @@ -84,7 +84,7 @@ fun HubScaffold( Icon(Icons.Rounded.ArrowBack, null) } }, - colors = if (toolbarOptions.alwaysVisible) TopAppBarDefaults.smallTopAppBarColors() else TopAppBarDefaults.smallTopAppBarColors( + colors = if (toolbarOptions.alwaysVisible) TopAppBarDefaults.topAppBarColors() else TopAppBarDefaults.topAppBarColors( containerColor = Color.Transparent, scrolledContainerColor = MaterialTheme.colorScheme.compositeSurfaceElevation( 3.dp diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/search/SearchScreen.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/search/SearchScreen.kt index 881df05..1c0ebac 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/search/SearchScreen.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/search/SearchScreen.kt @@ -2,7 +2,11 @@ package bruhcollective.itaysonlab.jetispot.ui.screens.search import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +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.layout.statusBarsPadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.pager.HorizontalPager @@ -12,7 +16,15 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Close import androidx.compose.material.icons.rounded.Search -import androidx.compose.material3.* +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier @@ -62,6 +74,7 @@ fun SearchScreen( Scaffold( topBar = { Column(Modifier.statusBarsPadding()) { + val containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(8.dp) OutlinedTextField( value = viewModel.searchQuery, onValueChange = { viewModel.searchQuery = it }, @@ -84,10 +97,11 @@ fun SearchScreen( .padding(16.dp) .fillMaxWidth() .focusTarget(), - colors = TextFieldDefaults.outlinedTextFieldColors( - containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation( - 8.dp - ), unfocusedBorderColor = MaterialTheme.colorScheme.surfaceVariant + colors = OutlinedTextFieldDefaults.colors( + focusedContainerColor = containerColor, + unfocusedContainerColor = containerColor, + disabledContainerColor = containerColor, + unfocusedBorderColor = MaterialTheme.colorScheme.surfaceVariant, ), singleLine = true, keyboardOptions = KeyboardOptions( From a7a5a5fd92f72edfeae21cca4d42ecc3e08ba644 Mon Sep 17 00:00:00 2001 From: Outlet7493 <122801553+Outlet7493@users.noreply.github.com> Date: Mon, 22 Jan 2024 01:50:08 +0000 Subject: [PATCH 16/17] manually place album/playlist image on findcard as sometimes background picture is only color --- .../jetispot/ui/hub/components/FindCard.kt | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/FindCard.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/FindCard.kt index e2ffc3f..4451040 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/FindCard.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/FindCard.kt @@ -1,11 +1,21 @@ package bruhcollective.itaysonlab.jetispot.ui.hub.components -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +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.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults import androidx.compose.material3.Text 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.draw.rotate import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow @@ -15,19 +25,31 @@ import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubItem import bruhcollective.itaysonlab.jetispot.ui.hub.clickableHub import bruhcollective.itaysonlab.jetispot.ui.shared.PreviewableAsyncImage +private val fallbackColors = listOf("#148a08", "#e1118c", "#27856a", "#283ea3", "#0d73ec", "#e8115b") + @Composable fun FindCard( item: HubItem ) { - Card(modifier = Modifier - .height(100.dp) - .fillMaxWidth() - .clickableHub(item)) { - Box { + Card( + modifier = Modifier + .height(100.dp) + .fillMaxWidth() + .clickableHub(item), + colors = CardDefaults.cardColors( + containerColor = Color(android.graphics.Color.parseColor((item.custom?.get("backgroundColor") as? String) ?: fallbackColors.random())) + ) + ) { + Box(Modifier.fillMaxSize()) { PreviewableAsyncImage( - imageUrl = item.images?.background?.uri, + imageUrl = item.images?.main?.uri, placeholderType = item.images?.background?.placeholder, - modifier = Modifier.fillMaxSize() + modifier = Modifier + .size(84.dp) + .offset(x = 24.dp, y = 12.dp) + .rotate(20f) + .clip(RoundedCornerShape(8.dp)) + .align(Alignment.BottomEnd) ) Text( item.text!!.title!!, @@ -37,8 +59,8 @@ fun FindCard( maxLines = 2, overflow = TextOverflow.Ellipsis, modifier = Modifier - .align(Alignment.TopStart) - .padding(12.dp) + .align(Alignment.TopStart) + .padding(12.dp) ) } } From e2b447c422d641c7e9ed26a70e5b5425955c24a0 Mon Sep 17 00:00:00 2001 From: Outlet7493 <122801553+Outlet7493@users.noreply.github.com> Date: Mon, 22 Jan 2024 01:53:12 +0000 Subject: [PATCH 17/17] neaten config screens --- .../ui/screens/config/ConfigScreen.kt | 6 +- .../ui/screens/config/SharedConfigUi.kt | 64 +++++++++++++++++-- .../ui/screens/config/StorageScreen.kt | 15 +++-- app/src/main/res/values/strings.xml | 2 +- 4 files changed, 72 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/config/ConfigScreen.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/config/ConfigScreen.kt index 972540c..00da7e0 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/config/ConfigScreen.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/config/ConfigScreen.kt @@ -32,7 +32,7 @@ class ConfigScreenViewModel @Inject constructor( ) : ViewModel(), ConfigViewModel { private val configList = buildList { - add(ConfigItem.Hint()) + add(ConfigItem.Hint) add(ConfigItem.Category(R.string.config_playback)) @@ -109,11 +109,11 @@ class ConfigScreenViewModel @Inject constructor( add(ConfigItem.Category(R.string.config_about)) - add(ConfigItem.Preference(R.string.app_name, { ctx, _ -> + add(ConfigItem.InfoItem(R.string.app_name) { ctx -> ctx.getString( R.string.about_version, BuildConfig.VERSION_NAME ) - }, {})) + }) add(ConfigItem.Preference(R.string.about_sources, { ctx, _ -> "" }, { it.openInBrowser("https://github.com/iTaysonLab/jetispot") diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/config/SharedConfigUi.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/config/SharedConfigUi.kt index e8e9c27..1970fd1 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/config/SharedConfigUi.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/config/SharedConfigUi.kt @@ -1,5 +1,6 @@ package bruhcollective.itaysonlab.jetispot.ui.screens.config +import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.net.Uri @@ -13,7 +14,14 @@ import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape @@ -22,8 +30,27 @@ import androidx.compose.material.icons.outlined.Translate import androidx.compose.material.icons.rounded.ArrowBack import androidx.compose.material.icons.rounded.EnergySavingsLeaf import androidx.compose.material.icons.rounded.Info -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeTopAppBar +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.RadioButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Slider +import androidx.compose.material3.Switch +import androidx.compose.material3.SwitchDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.collectAsState +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.draw.alpha @@ -53,6 +80,7 @@ interface ConfigViewModel { fun isRoot(): Boolean = false } +@SuppressLint("BatteryLife") @OptIn(ExperimentalMaterial3Api::class) @Composable fun BaseConfigScreen( @@ -118,6 +146,10 @@ fun BaseConfigScreen( ConfigInfo(stringResource(item.text)) } + is ConfigItem.InfoItem -> { + ConfigInfoItem(stringResource(item.title), item.subtitle(context)) + } + is ConfigItem.Preference -> { ConfigPreference( stringResource(item.title), @@ -288,6 +320,24 @@ fun ConfigRadio( } } +@Composable +fun ConfigInfoItem( + title: String, + subtitle: String +) { + Column(modifier = Modifier + .fillMaxWidth() + .padding(16.dp)) { + Text(text = title, color = MaterialTheme.colorScheme.onBackground, fontSize = 18.sp) + if (subtitle.isNotEmpty()) Text( + text = subtitle, + color = MaterialTheme.colorScheme.onSurfaceVariant, + fontSize = 14.sp, + modifier = Modifier.padding(top = 4.dp) + ) + } +} + @Composable fun ConfigPreference( title: String, @@ -415,6 +465,11 @@ sealed class ConfigItem { class Category(@StringRes val title: Int) : ConfigItem() class Info(@StringRes val text: Int) : ConfigItem() + class InfoItem( + @StringRes val title: Int, + val subtitle: (Context) -> String + ): ConfigItem() + class Preference( @StringRes val title: Int, val subtitle: (Context, AppConfig) -> String, @@ -451,6 +506,5 @@ sealed class ConfigItem { val modify: AppConfig.Builder.(Int) -> Unit ) : ConfigItem() - class Hint( - ) : ConfigItem() + data object Hint : ConfigItem() } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/config/StorageScreen.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/config/StorageScreen.kt index 60b5e15..a159692 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/config/StorageScreen.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/config/StorageScreen.kt @@ -115,6 +115,7 @@ fun StorageScreen( // 2-3. Header item("storageact") { + Spacer(Modifier.size(8.dp)) ConfigCategory(text = stringResource(id = R.string.storage_actions)) } @@ -131,7 +132,7 @@ fun StorageScreen( } @Composable -fun StorageComponentDetail( +private fun StorageComponentDetail( type: StorageViewModel.StorageFileKind, size: String, ) { @@ -139,7 +140,9 @@ fun StorageComponentDetail( Modifier .fillMaxWidth() .padding(horizontal = 16.dp) - .padding(top = 16.dp)) { + .padding(top = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { Icon(imageVector = type.icon, contentDescription = null, modifier = Modifier.size(28.dp)) Column(Modifier.padding(start = 16.dp)) { @@ -151,7 +154,7 @@ fun StorageComponentDetail( } @Composable -fun StorageHeader( +private fun StorageHeader( state: StorageViewModel.UiState.Ready ) { Column(Modifier.padding(horizontal = 16.dp)) { @@ -191,7 +194,7 @@ fun StorageHeader( .height(16.dp), state.takenTotal, state.others, state.internalStorage.total ) - Row(Modifier.padding(top = 4.dp, bottom = 8.dp)) { + Row(Modifier.padding(top = 8.dp, bottom = 8.dp)) { ProgressIndicatorLegend( modifier = Modifier.align(Alignment.CenterVertically), color = MaterialTheme.colorScheme.primary, @@ -211,7 +214,7 @@ fun StorageHeader( } @Composable -fun ProgressIndicatorLegend( +private fun ProgressIndicatorLegend( modifier: Modifier = Modifier, color: Color, text: String @@ -233,7 +236,7 @@ fun ProgressIndicatorLegend( } @Composable -fun MultiStateProgressIndicator( +private fun MultiStateProgressIndicator( modifier: Modifier, application: Long, others: Long, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cf974a4..dfa8a0f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -68,7 +68,7 @@ Plan includes See other plans - version %s + Version %s Show source code Open Telegram channel