From 3c6dd07f73c2fe675567da18be6f34e8f0374aa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Wed, 30 Nov 2022 00:09:00 +0100 Subject: [PATCH 01/39] Fixed Lyrics sync, started adding multi-language support and added a non-premium account card in Plan Overview --- app/build.gradle | 3 +- app/src/main/AndroidManifest.xml | 12 +- .../itaysonlab/jetispot/LocaleHelper.kt | 88 ++++++++++++++ .../itaysonlab/jetispot/MainActivity.kt | 27 +++++ .../itaysonlab/jetispot/SpApp.kt | 13 ++ .../jetispot/core/SpPlayerServiceImpl.kt | 8 +- .../jetispot/core/SpPlayerServiceManager.kt | 2 + .../core/lyrics/SpLyricsController.kt | 19 +-- .../itaysonlab/jetispot/ui/dac/DacRender.kt | 3 + .../BenefitListComponentBinder.kt | 2 - .../FallbackPlanComponentBinder.kt | 39 ++++++ app/src/main/res/values-es/strings.xml | 111 ++++++++++++++++++ app/src/main/res/values/strings.xml | 21 ++-- app/src/main/res/xml/locales_config.xml | 5 + 14 files changed, 329 insertions(+), 24 deletions(-) create mode 100644 app/src/main/java/bruhcollective/itaysonlab/jetispot/LocaleHelper.kt create mode 100644 app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_plans/FallbackPlanComponentBinder.kt create mode 100644 app/src/main/res/values-es/strings.xml create mode 100644 app/src/main/res/xml/locales_config.xml diff --git a/app/build.gradle b/app/build.gradle index 7a9516d7..6f1c25d1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -89,10 +89,11 @@ dependencies { implementation "androidx.core:core-ktx:1.9.0" implementation "androidx.palette:palette-ktx:1.0.0" implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1" + implementation "androidx.appcompat:appcompat:1.7.0-alpha01" // Compose implementation "androidx.navigation:navigation-compose:2.5.2" - implementation "androidx.activity:activity-compose:1.6.0" + implementation "androidx.activity:activity-compose:1.6.1" implementation "androidx.compose.material:material:$compose_version" implementation "androidx.compose.material3:material3:$compose_m3_version" implementation "androidx.compose.material:material-icons-extended:$compose_version" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c0f941f7..aa0748de 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,11 +19,12 @@ android:networkSecurityConfig="@xml/nsc" android:name=".SpApp" android:allowBackup="true" + android:localeConfig="@xml/locales_config" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/Theme.Jetispot" - tools:targetApi="n"> + tools:targetApi="tiramisu"> + + + + = Build.VERSION_CODES.N) { + updateResources(context, language) + } else updateResourcesLegacy(context, language) + } + + private fun getPersistedData(context: Context, defaultLanguage: String): String { + val preferences = PreferenceManager.getDefaultSharedPreferences(context) + return preferences.getString("Language", defaultLanguage)!! + } + + private fun persist(context: Context, language: String) { + val preferences = PreferenceManager.getDefaultSharedPreferences(context) + val editor = preferences.edit() + + editor.putString("Language", language) + editor.apply() + } + + @RequiresApi(Build.VERSION_CODES.N) + private fun updateResources(context: Context, language: String): Context { + val locale = Locale(language) + Locale.setDefault(locale) + + val configuration = context.resources.configuration + configuration.setLocale(locale) + configuration.setLayoutDirection(locale) + + return context.createConfigurationContext(configuration) + } + + private fun updateResourcesLegacy(context: Context, language: String): Context { + val locale = Locale(language) + Locale.setDefault(locale) + + val resources = context.resources + + val configuration = resources.configuration + + val config = Configuration() + if (language.isEmpty()) { + val emptyLocaleList = LocaleListCompat.getEmptyLocaleList() + config.setLocale(emptyLocaleList[0]) + } else { + config.setLocale(locale) + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + configuration.setLayoutDirection(locale) + } + + resources.updateConfiguration(configuration, resources.displayMetrics) + + return context + } + } +} diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/MainActivity.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/MainActivity.kt index b53d68e9..6deb7365 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/MainActivity.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/MainActivity.kt @@ -1,6 +1,9 @@ package bruhcollective.itaysonlab.jetispot +import android.content.Context import android.content.Intent +import android.content.res.Configuration +import android.os.Build import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.addCallback @@ -19,13 +22,17 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp +import androidx.core.os.LocaleListCompat import androidx.core.view.WindowCompat import androidx.navigation.NavController import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController +import bruhcollective.itaysonlab.jetispot.SpApp.Companion.applicationScope +import bruhcollective.itaysonlab.jetispot.SpApp.Companion.context import bruhcollective.itaysonlab.jetispot.core.SpAuthManager import bruhcollective.itaysonlab.jetispot.core.SpPlayerServiceManager import bruhcollective.itaysonlab.jetispot.core.SpSessionManager +import bruhcollective.itaysonlab.jetispot.core.util.Log import bruhcollective.itaysonlab.jetispot.ui.AppNavigation import bruhcollective.itaysonlab.jetispot.ui.ext.compositeSurfaceElevation import bruhcollective.itaysonlab.jetispot.ui.navigation.LocalNavigationController @@ -37,7 +44,11 @@ import com.google.accompanist.navigation.material.ExperimentalMaterialNavigation import com.google.accompanist.navigation.material.ModalBottomSheetLayout import com.google.accompanist.navigation.material.rememberBottomSheetNavigator import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import java.util.* import javax.inject.Inject @AndroidEntryPoint @@ -222,4 +233,20 @@ class MainActivity : ComponentActivity() { } } } + companion object{ + + fun updateLanguage(context: Context = SpApp.context, language: String) { + val locale = Locale(language) + Locale.setDefault(locale) + val config = Configuration() + if (language.isEmpty()) { + val emptyLocaleList = LocaleListCompat.getEmptyLocaleList() + config.setLocale(emptyLocaleList[0]) + } else { + config.setLocale(locale) + } + + context.resources.updateConfiguration(config, context.resources.displayMetrics) + } + } } diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/SpApp.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/SpApp.kt index 0f9d569f..57bb376b 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/SpApp.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/SpApp.kt @@ -1,10 +1,14 @@ package bruhcollective.itaysonlab.jetispot +import android.annotation.SuppressLint import android.app.Application +import android.content.Context import android.os.Build import bruhcollective.itaysonlab.jetispot.playback.sp.AndroidNativeDecoder import com.tencent.mmkv.MMKV import dagger.hilt.android.HiltAndroidApp +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob import org.slf4j.LoggerFactory import org.slf4j.impl.HandroidLoggerAdapter import xyz.gianlu.librespot.audio.decoders.Decoders @@ -24,6 +28,15 @@ class SpApp: Application() { override fun onCreate() { super.onCreate() + context = applicationContext + applicationScope = CoroutineScope(SupervisorJob()) MMKV.initialize(this, "${filesDir.absolutePath}/spa_meta") } + + companion object{ + lateinit var applicationScope: CoroutineScope + + @SuppressLint("StaticFieldLeak") + lateinit var context: Context + } } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/SpPlayerServiceImpl.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/SpPlayerServiceImpl.kt index 3be5da0e..2da02c72 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/SpPlayerServiceImpl.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/SpPlayerServiceImpl.kt @@ -9,6 +9,7 @@ import androidx.media2.common.SessionPlayer import androidx.media2.session.MediaController import androidx.media2.session.SessionCommandGroup import androidx.media2.session.SessionToken +import bruhcollective.itaysonlab.jetispot.core.lyrics.SpLyricsController import bruhcollective.itaysonlab.jetispot.core.util.Log import bruhcollective.itaysonlab.jetispot.playback.helpers.MediaItemWrapper import bruhcollective.itaysonlab.jetispot.playback.service.SpPlaybackService @@ -20,7 +21,7 @@ import kotlinx.coroutines.flow.flow class SpPlayerServiceImpl( private val context: Context, - private val manager: SpPlayerServiceManager + private val manager: SpPlayerServiceManager, ) : MediaController.ControllerCallback(), CoroutineScope by MainScope() { private var mediaController: MediaController? = null private var svcInit: (MediaController.() -> Unit)? = null @@ -32,8 +33,8 @@ class SpPlayerServiceImpl( if (manager.playbackState.value == SpPlayerServiceManager.PlaybackState.Playing) { emit(mediaController?.currentPosition ?: 0L) } - - delay(1000) + //Delay Ms + delay(50) } }.catch { emit(0L) @@ -138,7 +139,6 @@ class SpPlayerServiceImpl( p ) ) - manager.runExtra { it.onTrackProgressChanged(p) } diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/SpPlayerServiceManager.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/SpPlayerServiceManager.kt index 0f927726..6b97e16b 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/SpPlayerServiceManager.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/SpPlayerServiceManager.kt @@ -8,6 +8,8 @@ import androidx.compose.runtime.mutableStateOf import androidx.core.content.ContextCompat import androidx.core.net.toUri import androidx.core.util.Pair +import bruhcollective.itaysonlab.jetispot.core.lyrics.SpLyricsController +import bruhcollective.itaysonlab.jetispot.core.lyrics.SpLyricsRequester import bruhcollective.itaysonlab.jetispot.core.objs.player.PlayFromContextData import bruhcollective.itaysonlab.jetispot.core.objs.player.PlayFromContextPlayerData import bruhcollective.itaysonlab.jetispot.playback.helpers.MediaItemWrapper diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/lyrics/SpLyricsController.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/lyrics/SpLyricsController.kt index 682a5e4a..2adfa118 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/lyrics/SpLyricsController.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/lyrics/SpLyricsController.kt @@ -1,20 +1,21 @@ package bruhcollective.itaysonlab.jetispot.core.lyrics +import android.util.Log import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import bruhcollective.itaysonlab.jetispot.core.SpPlayerServiceManager import com.spotify.lyrics.v2.lyrics.proto.LyricsResponse.LyricsLine import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch import xyz.gianlu.librespot.common.Base62 -import xyz.gianlu.librespot.common.Utils -import xyz.gianlu.librespot.metadata.TrackId import javax.inject.Inject class SpLyricsController @Inject constructor( - private val requester: SpLyricsRequester + private val requester: SpLyricsRequester, + private val manager: SpPlayerServiceManager ): CoroutineScope by MainScope() { private val base62 = Base62.createInstanceWithInvertedCharacterSet() private var _songJob: Job? = null @@ -52,10 +53,14 @@ class SpLyricsController @Inject constructor( } } - fun setProgress(pos: Long) { - currentSongLine = currentLyricsLines.firstOrNull { - it.startTimeMs >= pos - }?.words ?: "" + fun setProgress(pos: Long){ + val lyricIndex = currentLyricsLines.indexOfLast { it.startTimeMs < pos } + + if (lyricIndex == -1) { + currentSongLine = "\uD83C\uDFB5" + } else { + currentSongLine = currentLyricsLines[lyricIndex].words + } } class ProviderInfo( 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 fa521da8..8429d12a 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 @@ -26,6 +26,7 @@ fun DacRender ( item: Message ) { when (item) { + // AllPlans / PlanOverview is MultiUserMemberComponent -> MultiUserMemberComponentBinder(item) is BenefitListComponent -> BenefitListComponentBinder(item) @@ -34,6 +35,8 @@ fun DacRender ( is SingleUserRecurringComponent -> SingleUserComponentBinder(item) is SingleUserPrepaidComponent -> SingleUserComponentBinder(item) is SingleUserTrialComponent -> SingleUserComponentBinder(item) + is FallbackPlanComponent -> FallbackPlanComponentBinder(item) + // Home is ToolbarComponent -> ToolbarComponentBinder(item) is ToolbarComponentV2 -> ToolbarComponent2Binder(item) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_plans/BenefitListComponentBinder.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_plans/BenefitListComponentBinder.kt index 2053c05e..1312da03 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_plans/BenefitListComponentBinder.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_plans/BenefitListComponentBinder.kt @@ -33,13 +33,11 @@ fun BenefitListComponentBinder( Row(Modifier.padding(16.dp)) { MediumText(text = stringResource(id = R.string.plan_includes)) } - Surface( tonalElevation = 8.dp, modifier = Modifier .height(1.dp) .fillMaxWidth() ) {} - item.benefitsList.forEach { benefit -> Row( Modifier diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_plans/FallbackPlanComponentBinder.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_plans/FallbackPlanComponentBinder.kt new file mode 100644 index 00000000..2e79f6ae --- /dev/null +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_plans/FallbackPlanComponentBinder.kt @@ -0,0 +1,39 @@ +package bruhcollective.itaysonlab.jetispot.ui.dac.components_plans + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import bruhcollective.itaysonlab.jetispot.ui.ext.compositeSurfaceElevation +import bruhcollective.itaysonlab.jetispot.ui.shared.MediumText +import com.spotify.planoverview.v1.FallbackPlanComponent + +@Composable +fun FallbackPlanComponentBinder( + item: FallbackPlanComponent +) { + Card( + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.compositeSurfaceElevation( + 3.dp + ) + ), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), + modifier = Modifier + .padding(top = 12.dp) + .padding(horizontal = 16.dp) + .fillMaxWidth() + ) { + Column(modifier = Modifier.padding(16.dp)) { + MediumText(modifier = Modifier, + text = item.name) + Divider() + MediumText(modifier = Modifier, + text = item.description) + Text("Seems like you don't have an Spotify Account") + } + } +} \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml new file mode 100644 index 00000000..ab121e9f --- /dev/null +++ b/app/src/main/res/values-es/strings.xml @@ -0,0 +1,111 @@ + + + Inicio + Buscar + Biblioteca + Ajustes + Bienvenido a Jetispot! + Inicia sesión en tu cuenta de Spotify para empezar! + Usuario o correo electrónico + Constraseña + Iniciar sesión + Avisos + Nombre de usuario o contraseña incorrecto + El campo de usuario o contraseña está vacío + Ajustes + Reproductor + Cuenta + Acerca de + Dispositivo + Calidad de audio + Baja + Normal + Alta + Muy alta + Normalización de audio + Deshabilitado + Silenciosa + Normal + Alta + %d segundos + Reproducir canciones sugeridas + Cuando la cola actual termine, se reproducirán canciones en base a tu algoritmo + Cerrar sesión + Sesión iniciada como %s + Ver plan actual + Cuenta premium + Resumen de tu plan + Planes premium + Administrador de planes + Niño + Desconocido + Este plan incluye + Ver otros planes + Versión %s + Ver código fuente + Abrir canal de telegram + Estos ajustes se aplicarán una vez reiniciada la app por completo + La calidad del audio puede variar dependiendo de la canción, el páis/región o algunos testeos a nivel interno de Spotify. Estos ajustes se aplicarán en la próxima canción que escuches. + "Ajusta el nivel de volumen a tu entorno. El ruido puede disminuir la calidad del audio. No afectará cuando esté seleccionado Normal o Silencioso. " + Activar + Cerrar sesión? + Esto eliminará todos los datos de la aplicación de tu dispositivo. Los ajustes no se borrarán. + Confirmar + Cancelar + Historial de escucha + Reproduciendo desde tu bliblioteca + Reproduciendo desde un álbum + Reproduciendo desde un artista + Reproduciendo desde una lista de reproducción + Reproduciendo desde un origen desconocido + Canciones que te gustan + Nuevos episodios + %s canciones + Última vez actualizada el + Filtrar por + Añadido recientemente + Título + Artista + Álbum + Invertir + Listas de reproducción + Álbums + Artistas + Podcasts + Crear un Blend + Generar invitación a grupo + Invita hasta 10 amigos a Blend, una playlist compartida que os recomienda canciones basada en un popurrí de vuestros gustos musicales. + Invitación a Blend + Almacenamiento + Metadatos de las canciones + para acelerar la carga de playlists o cola + Caché temporal + para guardar los datos de las canciones que más escichas + Caché de imágenes + para guardar las carátulas y disminuir el uso de internet + Puedes administrar el caché de la aplicación en esta pantalla. Algunos de los datos son eliminados automaticamente pasado un cierto período de tiempo. + usados + %s en total + Aplicación + Otros + Acciones + Borrar caché de la aplicación + Borrar los metadatos de las canciones + Ha ocurrido un error al cargar esta página :( + Copiar detalles + Recargar + Ocultar contraseña + Mostrar contraseña + Artista principal + Artista secundario + Remixer + Actor + Compositor + Dirigente + Orquesta + Rol desconocido + Inicio + Reciente + Buscar + Biblioteca + \ 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 f75072ff..4acbee61 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -15,7 +15,7 @@ Incorrect username or password. To use Jetispot, you must have a Spotify Premium account. Username or password field is empty. - + Jetispot is an unofficial application which is not connected in any ways to Spotify AB.\n\nBy entering your account details and pressing \"Sign in\", you confirm that:\n- you own your account\n- you have an active Spotify Premium subscription\n- you know that Jetispot can\'t download music, save it to offline storage or bypass DRM (and never will be) @@ -136,17 +136,20 @@ Hide password Show password - main artist - featured artist - remixer - actor - composer - conductor - orchestra - unknown role + Main artist + Featured artist + Remixer + Actor + Composer + Conductor + Orchestra + Unknown role Home Recent Browse Library + English + Spanish + System default \ No newline at end of file diff --git a/app/src/main/res/xml/locales_config.xml b/app/src/main/res/xml/locales_config.xml new file mode 100644 index 00000000..8c61fdfd --- /dev/null +++ b/app/src/main/res/xml/locales_config.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file From 52187174ae5dedc31f6782554927e2c0041eb3b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Thu, 1 Dec 2022 22:41:22 +0100 Subject: [PATCH 02/39] Fixed some UI components and bugs and also updated build.gradle files to Kotlin DSL --- app/build.gradle | 164 ------------- app/build.gradle.kts | 232 ++++++++++++++++++ app/proguard-rules.pro | 3 +- .../itaysonlab/jetispot/MainActivity.kt | 3 + .../jetispot/core/api/SpInternalApi.kt | 4 + .../core/objs/playlists/LikedSongsResponse.kt | 15 ++ .../ui/hub/virt/PlaylistEntityView.kt | 1 + .../nowplaying/NowPlayingMiniplayer.kt | 107 ++++---- .../ui/screens/nowplaying/NowPlayingScreen.kt | 3 +- .../fullscreen/NowPlayingLyricsComposition.kt | 3 +- .../YourLibraryContainerScreen.kt | 3 +- .../ui/shared/PreviewableAsyncImage.kt | 2 +- app/src/main/res/values-es/strings.xml | 8 + app/src/main/res/values/strings.xml | 15 +- build.gradle | 28 --- build.gradle.kts | 26 ++ settings.gradle => settings.gradle.kts | 5 +- 17 files changed, 364 insertions(+), 258 deletions(-) delete mode 100644 app/build.gradle create mode 100644 app/build.gradle.kts create mode 100644 app/src/main/java/bruhcollective/itaysonlab/jetispot/core/objs/playlists/LikedSongsResponse.kt delete mode 100644 build.gradle create mode 100644 build.gradle.kts rename settings.gradle => settings.gradle.kts (75%) diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index 6f1c25d1..00000000 --- a/app/build.gradle +++ /dev/null @@ -1,164 +0,0 @@ -plugins { - id "com.android.application" - id "org.jetbrains.kotlin.android" - id "dev.zacsweers.moshix" version "0.18.3" - id "dagger.hilt.android.plugin" - id "kotlin-kapt" - id "com.google.protobuf" version "0.8.18" -} - -android { - compileSdk 33 - - defaultConfig { - applicationId "bruhcollective.itaysonlab.jetispot" - minSdk 23 - targetSdk 33 - versionCode version_code - versionName version_name - resConfigs 'en' - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - sourceSets.main { - jniLibs.srcDir "src/main/libs" - } - - buildTypes { - release { - minifyEnabled true - shrinkResources true - proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" - } - } - - compileOptions { - coreLibraryDesugaringEnabled true - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = "1.8" - freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn" - } - - buildFeatures { - compose true - } - - composeOptions { - kotlinCompilerExtensionVersion compose_compiler_version - } - - kapt { - arguments { - arg("room.schemaLocation", "$projectDir/schemas") - } - } - - packagingOptions { - resources { - excludes += "/META-INF/*.kotlin_module" - excludes += "/META-INF/*.version" - excludes += "/META-INF/**" - excludes += "/kotlin/**" - excludes += "/kotlinx/**" - excludes += "**/*.properties" - excludes += "DebugProbesKt.bin" - excludes += "kotlin-tooling-metadata.json" - excludes += "/META-INF/{AL2.0,LGPL2.1}" - excludes += "log4j2.xml" - excludes += "**.proto" - } - } - namespace 'bruhcollective.itaysonlab.jetispot' -} - -moshi { - // Opt-in to enable moshi-sealed, disabled by default. - enableSealed.set(true) -} - -dependencies { - // Kotlin - implementation "org.jetbrains.kotlinx:kotlinx-datetime:0.4.0" - - // AndroidX - coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.2.0" - implementation "androidx.core:core-ktx:1.9.0" - implementation "androidx.palette:palette-ktx:1.0.0" - implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1" - implementation "androidx.appcompat:appcompat:1.7.0-alpha01" - - // Compose - implementation "androidx.navigation:navigation-compose:2.5.2" - implementation "androidx.activity:activity-compose:1.6.1" - implementation "androidx.compose.material:material:$compose_version" - implementation "androidx.compose.material3:material3:$compose_m3_version" - implementation "androidx.compose.material:material-icons-extended:$compose_version" - implementation "androidx.compose.ui:ui:$compose_version" - implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" - implementation "androidx.compose.ui:ui-util:$compose_version" - implementation "androidx.compose.ui:ui-tooling:$compose_version" - debugImplementation "androidx.customview:customview:1.2.0-alpha02" - debugImplementation "androidx.customview:customview-poolingcontainer:1.0.0" - - // Compose - Additions - implementation "com.google.accompanist:accompanist-navigation-material:$accompanist_version" - implementation "com.google.accompanist:accompanist-systemuicontroller:$accompanist_version" - implementation "com.google.accompanist:accompanist-pager:$accompanist_version" - - // Images - implementation "io.coil-kt:coil-compose:2.2.0" - - // DI - implementation "androidx.hilt:hilt-navigation-compose:1.0.0" - implementation "com.google.dagger:hilt-android:$hilt_version" - kapt "com.google.dagger:hilt-compiler:$hilt_version" - - // Playback - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.6.4" - implementation "com.gitlab.mvysny.slf4j:slf4j-handroid:1.7.30" - implementation "androidx.media2:media2-session:$media2_version" - implementation "androidx.media2:media2-player:$media2_version" - - // Librespot - implementation ("com.github.iTaysonLab.librespot-java:librespot-player:$librespot_commit:thin") { - exclude group: "xyz.gianlu.librespot", module: "librespot-sink" - exclude group: "com.lmax", module: "disruptor" - exclude group: "org.apache.logging.log4j" - } - - // Data - Network - 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" - - // Data - SQL - implementation "androidx.room:room-runtime:$room_version" - implementation "androidx.room:room-ktx:$room_version" - implementation "androidx.room:room-paging:$room_version" - kapt "androidx.room:room-compiler:$room_version" - - // Data - Proto - implementation "androidx.datastore:datastore:1.0.0" - implementation "com.google.protobuf:protobuf-java:3.21.5" - implementation "com.tencent:mmkv:1.2.14" -} - -protobuf { - protoc { - artifact = "com.google.protobuf:protoc:3.21.5" - } - - generateProtoTasks { - all().each { task -> - task.builtins { - java { - //option "lite" - } - } - } - } -} diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 00000000..3823e6e4 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,232 @@ +import java.io.FileInputStream +import java.util.Properties +import com.google.protobuf.gradle.* + +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("dev.zacsweers.moshix") version "0.18.3" + id("dagger.hilt.android.plugin") + id("kotlin-kapt") + id("com.google.protobuf") version "0.8.18" +} +apply(plugin = "dagger.hilt.android.plugin") + +val versionMajor = 0 +val versionMinor = 2 +val versionPatch = 1 +val versionBuild = 0 +val isStable = true + +val compose_version: String by rootProject.extra +val compose_m3_version: String by rootProject.extra +val compose_compiler_version: String by rootProject.extra +val media2_version: String by rootProject.extra +val accompanist_version: String by rootProject.extra +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 splitApks = !project.hasProperty("noSplits") + +android { + if (keystorePropertiesFile.exists()) { + val keystoreProperties = Properties() + keystoreProperties.load(FileInputStream(keystorePropertiesFile)) + signingConfigs { + getByName("debug") + { + keyAlias = keystoreProperties["keyAlias"].toString() + keyPassword = keystoreProperties["keyPassword"].toString() + storeFile = file(keystoreProperties["storeFile"]!!) + storePassword = keystoreProperties["storePassword"].toString() + } + } + } + + compileSdk = 33 + defaultConfig { + applicationId = "bruhcollective.itaysonlab.jetispot" + minSdk = 23 + targetSdk = 33 + versionCode = 1000 + versionName = StringBuilder("${versionMajor}.${versionMinor}.${versionPatch}").apply { + if (!isStable) append("-beta.${versionBuild}") + }.toString() + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + kapt { + arguments { + arg("room.schemaLocation", "$projectDir/schemas") + } + } + if (!splitApks) + ndk { + (properties["ABI_FILTERS"] as String).split(';').forEach { + abiFilters.add(it) + } + } + } + if (splitApks) + splits { + abi { + isEnable = !project.hasProperty("noSplits") + reset() + include("arm64-v8a", "armeabi-v7a", "x86", "x86_64") + isUniversalApk = false + } + } + //source sets in .kts + sourceSets { + getByName("main") { + java.srcDir("src/main/libs") + } + } + + buildTypes { + release { + isMinifyEnabled = true + isShrinkResources = true + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + if (keystorePropertiesFile.exists()) + signingConfig = signingConfigs.getByName("release") + matchingFallbacks.add(0, "debug") + matchingFallbacks.add(1, "release") + } + debug { + if (keystorePropertiesFile.exists()) + signingConfig = signingConfigs.getByName("debug") + matchingFallbacks.add(0, "debug") + matchingFallbacks.add(1, "release") + } + } + + compileOptions { + // coreLibraryDesugaringEnabled = true + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = "1.8" + freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn" + } + + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = compose_compiler_version + } + + packagingOptions { + resources { + excludes += "/META-INF/*.kotlin_module" + excludes += "/META-INF/*.version" + excludes += "/META-INF/**" + excludes += "/kotlin/**" + excludes += "/kotlinx/**" + excludes += "**/*.properties" + excludes += "DebugProbesKt.bin" + excludes += "kotlin-tooling-metadata.json" + excludes += "/META-INF/{AL2.0,LGPL2.1}" + excludes += "log4j2.xml" + excludes += "**.proto" + } + } + namespace = "bruhcollective.itaysonlab.jetispot" +} + +moshi { + // Opt-in to enable moshi-sealed, disabled by default. + enableSealed.set(true) +} + +dependencies { + // Kotlin + implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0") + + // AndroidX + coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.2.0") + implementation("androidx.core:core-ktx:1.9.0") + implementation("androidx.palette:palette-ktx:1.0.0") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.5.1") + implementation("androidx.appcompat:appcompat:1.7.0-alpha01") + + // Compose + implementation("androidx.navigation:navigation-compose:2.5.2") + implementation("androidx.activity:activity-compose:1.6.1") + implementation("androidx.compose.material:material:$compose_version") + implementation("androidx.compose.material3:material3:$compose_m3_version") + implementation("androidx.compose.material:material-icons-extended:$compose_version") + implementation("androidx.compose.ui:ui:$compose_version") + implementation("androidx.compose.ui:ui-tooling-preview:$compose_version") + implementation("androidx.compose.ui:ui-util:$compose_version") + implementation("androidx.compose.ui:ui-tooling:$compose_version") + debugImplementation("androidx.customview:customview:1.2.0-alpha02") + debugImplementation("androidx.customview:customview-poolingcontainer:1.0.0") + + // Compose - Additions + implementation("com.google.accompanist:accompanist-navigation-material:$accompanist_version") + implementation("com.google.accompanist:accompanist-systemuicontroller:$accompanist_version") + implementation("com.google.accompanist:accompanist-pager:$accompanist_version") + + // Images + implementation("io.coil-kt:coil-compose:2.2.0") + + // DI + implementation("androidx.hilt:hilt-navigation-compose:1.0.0") + implementation("com.google.dagger:hilt-android:$hilt_version") + kapt("com.google.dagger:hilt-compiler:$hilt_version") + + // Playback + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.6.4") + implementation("com.gitlab.mvysny.slf4j:slf4j-handroid:1.7.30") + implementation("androidx.media2:media2-session:$media2_version") + implementation("androidx.media2:media2-player:$media2_version") + + // Librespot + implementation("com.github.iTaysonLab.librespot-java:librespot-player:$librespot_commit:thin") { + exclude(group = "xyz.gianlu.librespot", module = "librespot-sink") + exclude(group = "com.lmax", module = "disruptor") + exclude(group = "org.apache.logging.log4j") + } + + // Data - Network + 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") + + // Data - SQL + implementation("androidx.room:room-runtime:$room_version") + implementation("androidx.room:room-ktx:$room_version") + implementation("androidx.room:room-paging:$room_version") + kapt("androidx.room:room-compiler:$room_version") + + // Data - Proto + implementation("androidx.datastore:datastore:1.0.0") + implementation("com.google.protobuf:protobuf-java:3.21.5") + implementation("com.tencent:mmkv:1.2.14") +} + +//https://stackoverflow.com/questions/65390807/unresolved-reference-protoc-when-using-gradle-protocol-buffers +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:3.21.5" + } + + generateProtoTasks { + all().forEach { + it.builtins { + create("java") { + //option("lite") + } + } + } + } +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 7f9fc50f..f224b209 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,6 +1,6 @@ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. +# proguardFiles setting in build.gradle.kts. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html @@ -83,7 +83,6 @@ -keepclassmembers,allowshrinking,allowobfuscation interface * { @retrofit2.http.* ; } --dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement -dontwarn javax.annotation.** -dontwarn kotlin.Unit -dontwarn retrofit2.KotlinExtensions diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/MainActivity.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/MainActivity.kt index 6deb7365..47dbbd46 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/MainActivity.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/MainActivity.kt @@ -78,6 +78,9 @@ class MainActivity : ComponentActivity() { @OptIn(ExperimentalMaterialApi::class, ExperimentalMaterialNavigationApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + runBlocking{ + updateLanguage(context, LocaleHelper.getLanguage(context)) + } WindowCompat.setDecorFitsSystemWindows(window, false) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt index b57252eb..e8e4341c 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt @@ -1,6 +1,7 @@ package bruhcollective.itaysonlab.jetispot.core.api import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubResponse +import bruhcollective.itaysonlab.jetispot.core.objs.playlists.LikedSongsResponse import bruhcollective.itaysonlab.jetispot.core.objs.tags.ContentFilterResponse import bruhcollective.itaysonlab.jetispot.core.util.SpUtils import bruhcollective.itaysonlab.jetispot.proto.SearchViewResponse @@ -25,6 +26,9 @@ interface SpInternalApi { @GET("/radio-apollo/v5/radio-hub") suspend fun getRadioHub(): HubResponse + @GET("/me/tracks") + suspend fun getSavedTracks(): LikedSongsResponse + @GET("/hubview-mobile-v1/browse/{id}") suspend fun getBrowseView(@Path("id") pageId: String = ""): HubResponse diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/objs/playlists/LikedSongsResponse.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/objs/playlists/LikedSongsResponse.kt new file mode 100644 index 00000000..e21f6961 --- /dev/null +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/objs/playlists/LikedSongsResponse.kt @@ -0,0 +1,15 @@ +package bruhcollective.itaysonlab.jetispot.core.objs.playlists + +import bruhcollective.itaysonlab.jetispot.ui.hub.virt.PlaylistEntityView +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +class LikedSongsResponse( + val href: String? = "", + val items: List, + val limit: Int? = 0, + val next: String? = "", + val offset: Int? = 0, + val previous: String? = "", + val total: Int? = 0, +) \ 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 7ad996d5..c455dada 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 @@ -8,6 +8,7 @@ import bruhcollective.itaysonlab.jetispot.core.objs.hub.* import bruhcollective.itaysonlab.jetispot.core.objs.player.* 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 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 12c6422c..2016fafc 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 @@ -1,5 +1,8 @@ package bruhcollective.itaysonlab.jetispot.ui.screens.nowplaying +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.shape.RoundedCornerShape @@ -21,65 +24,69 @@ import bruhcollective.itaysonlab.jetispot.ui.shared.PreviewableSyncImage @Composable fun NowPlayingMiniplayer( viewModel: NowPlayingViewModel, - modifier: Modifier + modifier: Modifier, + visible: Boolean ) { - Surface(tonalElevation = 8.dp, modifier = modifier) { - Box(Modifier.fillMaxSize()) { - LinearProgressIndicator( - progress = viewModel.currentPosition.value.progressRange, - color = MaterialTheme.colorScheme.primary, - modifier = Modifier - .height(2.dp) - .fillMaxWidth() - ) - - Row( - Modifier - .fillMaxHeight() - .padding(horizontal = 16.dp) - ) { - PreviewableSyncImage( - viewModel.currentTrack.value.artworkCompose, - placeholderType = "track", + AnimatedVisibility(visible = visible, enter = fadeIn(), exit = fadeOut()) { + Surface(tonalElevation = 8.dp, modifier = modifier) { + Box(Modifier.fillMaxSize()) { + LinearProgressIndicator( + progress = viewModel.currentPosition.value.progressRange, + color = MaterialTheme.colorScheme.primary, modifier = Modifier - .size(48.dp) - .align(Alignment.CenterVertically) - .clip(RoundedCornerShape(8.dp)) + .height(2.dp) + .fillMaxWidth() ) - Column( + Row( Modifier - .weight(2f) - .padding(horizontal = 14.dp) - .align(Alignment.CenterVertically) + .fillMaxHeight() + .padding(horizontal = 16.dp) ) { - Text( - viewModel.currentTrack.value.title, - color = MaterialTheme.colorScheme.onSurface, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - fontSize = 16.sp + PreviewableSyncImage( + viewModel.currentTrack.value.artworkCompose, + placeholderType = "track", + modifier = Modifier + .size(48.dp) + .align(Alignment.CenterVertically) + .clip(RoundedCornerShape(8.dp)) ) - Text( - viewModel.currentTrack.value.artist, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - fontSize = 12.sp, - modifier = Modifier.padding(top = 2.dp) + + Column( + Modifier + .weight(2f) + .padding(horizontal = 14.dp) + .align(Alignment.CenterVertically) + ) { + Text( + viewModel.currentTrack.value.title, + color = MaterialTheme.colorScheme.onSurface, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontSize = 16.sp + ) + Text( + viewModel.currentTrack.value.artist, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontSize = 12.sp, + modifier = Modifier.padding(top = 2.dp) + ) + } + + PlayPauseButton( + viewModel.currentState.value == SpPlayerServiceManager.PlaybackState.Playing, + MaterialTheme.colorScheme.onSurface, + Modifier + .fillMaxHeight() + .width(56.dp) + .align(Alignment.CenterVertically) + .clickable { + viewModel.togglePlayPause() + } ) } - - PlayPauseButton( - viewModel.currentState.value == SpPlayerServiceManager.PlaybackState.Playing, - MaterialTheme.colorScheme.onSurface, - Modifier - .fillMaxHeight() - .width(56.dp) - .align(Alignment.CenterVertically).clickable { - viewModel.togglePlayPause() - } - ) } } } 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 e9fcfcba..fc82a90d 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 @@ -47,7 +47,8 @@ fun NowPlayingScreen( .clickable { scope.launch { bottomSheetState.expand() } } .fillMaxWidth() .height(72.dp) - .align(Alignment.TopStart) + .align(Alignment.TopStart), + visible = bsOffset() <= 0.99f ) } } diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingLyricsComposition.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingLyricsComposition.kt index b2bb8940..e2f7f338 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingLyricsComposition.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingLyricsComposition.kt @@ -68,9 +68,10 @@ fun NowPlayingLyricsComposition( IntOffset(x = 0, y = (48.dp.toPx() * (1f - rvStateProgress)).toInt()) }) { - LazyColumn { + LazyColumn(modifier = Modifier.padding(12.dp)) { items(viewModel.spLyricsController.currentLyricsLines) { line -> Text(text = line.words, color = Color.White, fontSize = 18.sp, fontWeight = FontWeight.SemiBold) + Spacer(modifier = Modifier.height(4.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 377486c4..da0a53f5 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 @@ -19,6 +19,7 @@ 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.collection.db.LocalCollectionDao import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.CollectionEntry import bruhcollective.itaysonlab.jetispot.core.collection.db.model2.PredefCeType @@ -50,7 +51,7 @@ fun YourLibraryContainerScreen( Scaffold(topBar = { Column { TopAppBar(title = { - Text("Your Library") + Text(stringResource(id = R.string.your_library)) }, navigationIcon = { IconButton(onClick = { /* TODO */ }) { Icon(Icons.Rounded.AccountCircle, null) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/shared/PreviewableAsyncImage.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/shared/PreviewableAsyncImage.kt index 7f1a0bf4..8ed9051b 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/shared/PreviewableAsyncImage.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/shared/PreviewableAsyncImage.kt @@ -25,7 +25,7 @@ fun PreviewableAsyncImage ( placeholderType: String?, modifier: Modifier ) { - if (imageUrl.isNullOrEmpty() || imageUrl == "https://i.scdn.co/image/") { + if (imageUrl.isNullOrEmpty() || imageUrl == "https://i.scdn.co/image/" || imageUrl.startsWith("spotify:mosaic")) { Box(modifier) { ImagePreview(placeholderType, modifier) } diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index ab121e9f..63fc41f2 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -108,4 +108,12 @@ Reciente Buscar Biblioteca + Tu biblioteca + Predeterminado del sistema + Para usar Jetispot necesitas una cuenta premium... O no jeje + Crossfade + Deshabilitado + Miembro del plan + Inglés + Español \ 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 4acbee61..33163e71 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -29,22 +29,22 @@ Audio quality Low - ~ 24kbit/s + ~ 24kbit/s Normal - ~ 96kbit/s + ~ 96kbit/s High - ~ 160kbit/s + ~ 160kbit/s Very high - ~ 320kbit/s + ~ 320kbit/s Audio normalization Disabled Quiet - -5 dB + -5 dB Normal - 3 dB + 3 dB Loud - 6 dB + 6 dB Crossfade Disabled @@ -152,4 +152,5 @@ English Spanish System default + Your library \ No newline at end of file diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 46804899..00000000 --- a/build.gradle +++ /dev/null @@ -1,28 +0,0 @@ -buildscript { - ext { - version_code = 13 - version_name = "poc_v13" - - compose_version = "1.3.0-rc01" - compose_m3_version = "1.0.0-rc01" - compose_compiler_version = "1.3.1" - - media2_version = "1.2.1" - accompanist_version = "0.26.5-rc" - room_version = "2.5.0-beta01" - - librespot_commit = "e95c4f0529" - hilt_version = "2.43.2" - } -} - -plugins { - id "com.android.application" version '7.3.1' apply false - id "com.android.library" version '7.3.1' apply false - id "org.jetbrains.kotlin.android" version "1.7.10" apply false - id "com.google.dagger.hilt.android" version "$hilt_version" apply false -} - -task clean(type: Delete) { - delete rootProject.buildDir -} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 00000000..e37303e0 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,26 @@ +buildscript { + val version_code by extra(13) + val version_name by extra("poc_v13") + + val compose_version by extra("1.3.0-rc01") + val compose_m3_version by extra("1.0.0-rc01") + val compose_compiler_version by extra("1.3.1") + + val media2_version by extra("1.2.1") + val accompanist_version by extra("0.26.5-rc") + val room_version by extra("2.5.0-beta01") + + val librespot_commit by extra("e95c4f0529") + val hilt_version by extra("2.43.2") +} + +plugins { + id("com.android.application") version "7.3.1" apply false + id("com.android.library") version "7.3.1" apply false + id("org.jetbrains.kotlin.android") version "1.7.10" apply false + id("com.google.dagger.hilt.android") version "2.43.2" apply false +} + +tasks.register("clean", Delete::class) { + delete(rootProject.buildDir) +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle.kts similarity index 75% rename from settings.gradle rename to settings.gradle.kts index 83e105f4..bba2d9f5 100644 --- a/settings.gradle +++ b/settings.gradle.kts @@ -3,7 +3,6 @@ pluginManagement { gradlePluginPortal() google() mavenCentral() - maven { url 'https://jitpack.io' } } } dependencyResolutionManagement { @@ -11,8 +10,8 @@ dependencyResolutionManagement { repositories { google() mavenCentral() - maven { url 'https://jitpack.io' } + maven ("https://jitpack.io") } } rootProject.name = "Jetispot" -include ':app' +include(":app") From 6df00e128f6b0bfba096fe52334f357b8e36d339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Fri, 2 Dec 2022 21:24:49 +0100 Subject: [PATCH 03/39] Fixed (partially) searching, moved some hard coded text to resources and formatted some code --- .gitignore | 7 + app/proguard-rules.pro | 29 ++- .../jetispot/core/SpMetadataRequester.kt | 201 ++++++++++-------- .../jetispot/core/SpPlayerServiceManager.kt | 2 + .../jetispot/core/metadata_db/SpMetadataDb.kt | 2 +- .../playback/helpers/MediaItemWrapper.kt | 1 + .../ui/blocks/TwoColumnAndImageBlock.kt | 29 ++- .../itaysonlab/jetispot/ui/hub/HubBinder.kt | 2 + .../ui/hub/components/GridMediumCard.kt | 65 ++++-- .../ui/hub/components/ShortcutsCard.kt | 34 ++- .../ui/screens/hub/LikedSongsScreen.kt | 2 + .../nowplaying/NowPlayingMiniplayer.kt | 124 +++++------ .../ui/screens/search/SearchScreen.kt | 23 +- .../ui/screens/search/SearchViewModel.kt | 19 +- app/src/main/res/values-es/strings.xml | 3 + app/src/main/res/values/strings.xml | 12 ++ 16 files changed, 362 insertions(+), 193 deletions(-) diff --git a/.gitignore b/.gitignore index aa724b77..f9f6498d 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,10 @@ .externalNativeBuild .cxx local.properties +/.idea/.name +/app/release/app-arm64-v8a-release.apk +/app/release/app-armeabi-v7a-release.apk +/app/release/app-x86-release.apk +/app/release/app-x86_64-release.apk +/jetispotkeypass.jks +/app/release/output-metadata.json diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index f224b209..ac9a20ba 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -91,4 +91,31 @@ -keep,allowobfuscation interface <1> -keep,allowobfuscation,allowshrinking interface retrofit2.Call -keep,allowobfuscation,allowshrinking class retrofit2.Response --keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation \ No newline at end of file +-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation +-dontwarn javax.sound.sampled.AudioFormat$Encoding +-dontwarn javax.sound.sampled.AudioFormat +-dontwarn javax.sound.sampled.AudioSystem +-dontwarn javax.sound.sampled.Control$Type +-dontwarn javax.sound.sampled.Control +-dontwarn javax.sound.sampled.DataLine$Info +-dontwarn javax.sound.sampled.FloatControl$Type +-dontwarn javax.sound.sampled.FloatControl +-dontwarn javax.sound.sampled.Line$Info +-dontwarn javax.sound.sampled.Line +-dontwarn javax.sound.sampled.LineUnavailableException +-dontwarn javax.sound.sampled.Mixer$Info +-dontwarn javax.sound.sampled.Mixer +-dontwarn javax.sound.sampled.SourceDataLine +-dontwarn kotlinx.serialization.KSerializer +-dontwarn kotlinx.serialization.Serializable +-dontwarn org.apache.logging.log4j.Level +-dontwarn org.apache.logging.log4j.core.config.Configurator +-dontwarn org.bouncycastle.jsse.BCSSLParameters +-dontwarn org.bouncycastle.jsse.BCSSLSocket +-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider +-dontwarn org.conscrypt.Conscrypt$Version +-dontwarn org.conscrypt.Conscrypt +-dontwarn org.conscrypt.ConscryptHostnameVerifier +-dontwarn org.openjsse.javax.net.ssl.SSLParameters +-dontwarn org.openjsse.javax.net.ssl.SSLSocket +-dontwarn org.openjsse.net.ssl.OpenJSSE \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/SpMetadataRequester.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/SpMetadataRequester.kt index a7dbc326..dccf8393 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/SpMetadataRequester.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/SpMetadataRequester.kt @@ -16,109 +16,138 @@ import javax.inject.Singleton @Singleton class SpMetadataRequester @Inject constructor( - private val spSessionManager: SpSessionManager, - private val spMetadataDb: SpMetadataDb + private val spSessionManager: SpSessionManager, + private val spMetadataDb: SpMetadataDb ) { - companion object { - private val coreKinds = arrayOf( - ExtensionKindOuterClass.ExtensionKind.EPISODE_V4, - ExtensionKindOuterClass.ExtensionKind.SHOW_V4, - ExtensionKindOuterClass.ExtensionKind.ARTIST_V4, - ExtensionKindOuterClass.ExtensionKind.ALBUM_V4, - ExtensionKindOuterClass.ExtensionKind.TRACK_V4, - ExtensionKindOuterClass.ExtensionKind.USER_PROFILE - ) - } - - private inline fun MutableMap.runOrAdd(key: K, ifExists: (V) -> Unit, ifNotExists: () -> V) { - val value = get(key) - if (value != null) { - ifExists(value) - } else { - put(key, ifNotExists()) + companion object { + private val coreKinds = arrayOf( + ExtensionKindOuterClass.ExtensionKind.EPISODE_V4, + ExtensionKindOuterClass.ExtensionKind.SHOW_V4, + ExtensionKindOuterClass.ExtensionKind.ARTIST_V4, + ExtensionKindOuterClass.ExtensionKind.ALBUM_V4, + ExtensionKindOuterClass.ExtensionKind.TRACK_V4, + ExtensionKindOuterClass.ExtensionKind.USER_PROFILE + ) } - } - - suspend fun request(builder: MutableRequestEntities.() -> Unit): UnpackedMetadataResponse = request(buildList(builder)) - suspend fun request(entities: RequestEntities) = withContext(Dispatchers.IO) { - val result = UnpackedMetadataResponse(emptyList()) - if (entities.isEmpty()) return@withContext result - - val alreadyCached = mutableMapOf() - val shouldRequest = mutableMapOf() - - entities.forEach { entity -> - val uri = entity.first - entity.second.forEach { kind -> - val kindInDbUri = if (coreKinds.contains(kind)) uri else "$uri:${kind.ordinal}" - if (spMetadataDb.contains(kindInDbUri)) { - val extensionData = EntityExtensionDataOuterClass.EntityExtensionData.newBuilder().setEntityUri(uri).setExtensionData(Any.newBuilder().setValue(ByteString.copyFrom(spMetadataDb.get(kindInDbUri))).build()).build() - alreadyCached.runOrAdd(kind, ifExists = { - it.addExtensionData(extensionData) - }, ifNotExists = { - ExtendedMetadata.EntityExtensionDataArray.newBuilder().setExtensionKind(kind).addExtensionData(extensionData) - }) + + private inline fun MutableMap.runOrAdd( + key: K, + ifExists: (V) -> Unit, + ifNotExists: () -> V + ) { + val value = get(key) + if (value != null) { + ifExists(value) } else { - val query = ExtendedMetadata.ExtensionQuery.newBuilder().setExtensionKind(kind).build() - shouldRequest.runOrAdd(uri, ifExists = { - it.addQuery(query) - }, ifNotExists = { - ExtendedMetadata.EntityRequest.newBuilder().setEntityUri(uri).addQuery(query) - }) + put(key, ifNotExists()) } - } } - result += UnpackedMetadataResponse(alreadyCached.map { it.value.build() }) + suspend fun request(builder: MutableRequestEntities.() -> Unit): UnpackedMetadataResponse = + request(buildList(builder)) + + suspend fun request(entities: RequestEntities) = withContext(Dispatchers.IO) { + val result = UnpackedMetadataResponse(emptyList()) + if (entities.isEmpty()) return@withContext result + + val alreadyCached = + mutableMapOf() + val shouldRequest = mutableMapOf() + + entities.forEach { entity -> + val uri = entity.first + entity.second.forEach { kind -> + val kindInDbUri = if (coreKinds.contains(kind)) uri else "$uri:${kind.ordinal}" + if (spMetadataDb.contains(kindInDbUri)) { + val extensionData = + EntityExtensionDataOuterClass.EntityExtensionData.newBuilder() + .setEntityUri(uri).setExtensionData( + Any.newBuilder() + .setValue(ByteString.copyFrom(spMetadataDb.get(kindInDbUri))) + .build() + ).build() + alreadyCached.runOrAdd(kind, ifExists = { + it.addExtensionData(extensionData) + }, ifNotExists = { + ExtendedMetadata.EntityExtensionDataArray.newBuilder() + .setExtensionKind(kind).addExtensionData(extensionData) + }) + } else { + val query = + ExtendedMetadata.ExtensionQuery.newBuilder().setExtensionKind(kind).build() + shouldRequest.runOrAdd(uri, ifExists = { + it.addQuery(query) + }, ifNotExists = { + ExtendedMetadata.EntityRequest.newBuilder().setEntityUri(uri) + .addQuery(query) + }) + } + } + } + + result += UnpackedMetadataResponse(alreadyCached.map { it.value.build() }) + + shouldRequest.map { it.value.build() }.chunked(400).forEach { + result += requestNetwork(it) + } - shouldRequest.map { it.value.build() }.chunked(400).forEach { - result += requestNetwork(it) + return@withContext result } - return@withContext result - } - - private fun requestNetwork( - queries: List - ): UnpackedMetadataResponse { - return try { - UnpackedMetadataResponse(spSessionManager.session.api().getExtendedMetadata( - ExtendedMetadata.BatchedEntityRequest.newBuilder().addAllEntityRequest(queries).build() - ).extendedMetadataList.also { saveUris(it) }) - } catch (e: Exception) { - e.printStackTrace() - UnpackedMetadataResponse(emptyList()) + private fun requestNetwork( + queries: List + ): UnpackedMetadataResponse { + return try { + UnpackedMetadataResponse(spSessionManager.session.api().getExtendedMetadata( + ExtendedMetadata.BatchedEntityRequest.newBuilder().addAllEntityRequest(queries) + .build() + ).extendedMetadataList.also { saveUris(it) }) + } catch (e: Exception) { + e.printStackTrace() + UnpackedMetadataResponse(emptyList()) + } } - } - - private fun saveUris(data: List) { - data.forEach { arr -> - val kind = arr.extensionKind - arr.extensionDataList.forEach { toSave -> - spMetadataDb.put(toSave.entityUri.let { uri -> - if (coreKinds.contains(kind)) uri else "$uri:${kind.ordinal}" - }, toSave.extensionData.value.toByteArray()) - } + + private fun saveUris(data: List) { + data.forEach { arr -> + val kind = arr.extensionKind + arr.extensionDataList.forEach { toSave -> + spMetadataDb.put(toSave.entityUri.let { uri -> + if (coreKinds.contains(kind)) uri else "$uri:${kind.ordinal}" + }, toSave.extensionData.value.toByteArray()) + } + } } - } } typealias MutableRequestEntities = MutableList>> typealias RequestEntities = List>> -fun MutableRequestEntities.user(uri: String) = add(uri to listOf(ExtensionKindOuterClass.ExtensionKind.USER_PROFILE)) -fun MutableRequestEntities.album(uri: String) = add(uri to listOf(ExtensionKindOuterClass.ExtensionKind.ALBUM_V4)) -fun MutableRequestEntities.artist(uri: String) = add(uri to listOf(ExtensionKindOuterClass.ExtensionKind.ARTIST_V4)) -fun MutableRequestEntities.track(uri: String) = add(uri to listOf(ExtensionKindOuterClass.ExtensionKind.TRACK_V4)) +fun MutableRequestEntities.user(uri: String) = + add(uri to listOf(ExtensionKindOuterClass.ExtensionKind.USER_PROFILE)) + +fun MutableRequestEntities.album(uri: String) = + add(uri to listOf(ExtensionKindOuterClass.ExtensionKind.ALBUM_V4)) + +fun MutableRequestEntities.artist(uri: String) = + add(uri to listOf(ExtensionKindOuterClass.ExtensionKind.ARTIST_V4)) + +fun MutableRequestEntities.track(uri: String) = + add(uri to listOf(ExtensionKindOuterClass.ExtensionKind.TRACK_V4)) + +fun MutableRequestEntities.tracks(uris: List) = + addAll(uris.map { it to listOf(ExtensionKindOuterClass.ExtensionKind.TRACK_V4) }) + +fun MutableRequestEntities.episodes(uris: List) = + addAll(uris.map { it to listOf(ExtensionKindOuterClass.ExtensionKind.EPISODE_V4) }) -fun MutableRequestEntities.tracks(uris: List) = addAll(uris.map { it to listOf(ExtensionKindOuterClass.ExtensionKind.TRACK_V4) }) -fun MutableRequestEntities.episodes(uris: List) = addAll(uris.map { it to listOf(ExtensionKindOuterClass.ExtensionKind.EPISODE_V4) }) -fun MutableRequestEntities.raw(uris: List) = addAll(uris.map { it to listOf(spotifyIdToKind(it)) }) +fun MutableRequestEntities.raw(uris: List) = + addAll(uris.map { it to listOf(spotifyIdToKind(it)) }) private fun spotifyIdToKind(id: String) = when (id.split(":")[1]) { - "track" -> ExtensionKindOuterClass.ExtensionKind.TRACK_V4 - "album" -> ExtensionKindOuterClass.ExtensionKind.ALBUM_V4 - "artist" -> ExtensionKindOuterClass.ExtensionKind.ARTIST_V4 - "episode" -> ExtensionKindOuterClass.ExtensionKind.EPISODE_V4 - else -> ExtensionKindOuterClass.ExtensionKind.UNKNOWN_EXTENSION + "track" -> ExtensionKindOuterClass.ExtensionKind.TRACK_V4 + "album" -> ExtensionKindOuterClass.ExtensionKind.ALBUM_V4 + "artist" -> ExtensionKindOuterClass.ExtensionKind.ARTIST_V4 + "episode" -> ExtensionKindOuterClass.ExtensionKind.EPISODE_V4 + else -> ExtensionKindOuterClass.ExtensionKind.UNKNOWN_EXTENSION } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/SpPlayerServiceManager.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/SpPlayerServiceManager.kt index 6b97e16b..8f8ac7ce 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/SpPlayerServiceManager.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/SpPlayerServiceManager.kt @@ -2,6 +2,7 @@ package bruhcollective.itaysonlab.jetispot.core import android.content.Context import android.os.Bundle +import android.util.Log import androidx.annotation.FloatRange import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf @@ -64,6 +65,7 @@ class SpPlayerServiceManager @Inject constructor( fun play(data: PlayFromContextData) = play(data.uri, data.player) fun play(uri: String, data: PlayFromContextPlayerData) = impl.awaitService { + Log.i("SpPlayerServiceManager", "Trying to play audio with $uri as URI and $data as data") setMediaUri(uri.toUri(), Bundle().also { it.putString("sp_json", moshi.adapter().toJson(data)) }) } diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/metadata_db/SpMetadataDb.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/metadata_db/SpMetadataDb.kt index d44c80ec..5c002bc7 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/metadata_db/SpMetadataDb.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/metadata_db/SpMetadataDb.kt @@ -15,7 +15,7 @@ class SpMetadataDb @Inject constructor( fun contains(uri: String) = instance.containsKey(uri) - fun get(uri: String): ByteArray = instance.getBytes(uri, null)!! + fun get(uri: String): ByteArray = instance.getBytes(uri, null) fun put(uri: String, msg: ByteArray) = instance.encode(uri, msg) fun clear() = instance.clearAll() diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/playback/helpers/MediaItemWrapper.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/playback/helpers/MediaItemWrapper.kt index 39e37986..aedb7254 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/playback/helpers/MediaItemWrapper.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/playback/helpers/MediaItemWrapper.kt @@ -3,6 +3,7 @@ package bruhcollective.itaysonlab.jetispot.playback.helpers import androidx.compose.ui.graphics.asImageBitmap import androidx.media2.common.MediaItem import androidx.media2.common.MediaMetadata +import bruhcollective.itaysonlab.jetispot.R class MediaItemWrapper( private val item: MediaItem? = null diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/blocks/TwoColumnAndImageBlock.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/blocks/TwoColumnAndImageBlock.kt index 0c3da514..b858de69 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/blocks/TwoColumnAndImageBlock.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/blocks/TwoColumnAndImageBlock.kt @@ -15,18 +15,33 @@ import xyz.gianlu.librespot.metadata.ImageId @Composable fun TwoColumnAndImageBlock( - artworkUri: String?, + artworkUri: String? = "spotify:image:ab67706c0000bebb8d0ce13d55f634e290f744ba", title: String, text: String, modifier: Modifier = Modifier ) { - Row(modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 12.dp)) { - PreviewableAsyncImage(imageUrl = remember(artworkUri) { - artworkUri?.let { "https://i.scdn.co/image/" + ImageId.fromUri(it).hexId() } - }, placeholderType = "track", modifier = Modifier - .size(48.dp)) + Row( + modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 12.dp)) { + PreviewableAsyncImage( + imageUrl = remember(artworkUri) { + artworkUri?.let { + "https://i.scdn.co/image/" + ImageId.fromUri( + if (artworkUri.startsWith( + "spotify:mosaic" + ) + ) "spotify:image:ab67706c0000bebb8d0ce13d55f634e290f744ba" else it + ).hexId() + } + }, placeholderType = "track", modifier = Modifier + .size(48.dp) + ) - Column(Modifier.padding(horizontal = 12.dp).align(Alignment.CenterVertically)) { + Column( + Modifier + .padding(horizontal = 12.dp) + .align(Alignment.CenterVertically)) { MediumText(title, fontWeight = FontWeight.Normal, fontSize = 18.sp) Spacer(Modifier.height(4.dp)) Subtext(text, modifier = Modifier, maxLines = 1) 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 61003832..aa998b43 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,6 +10,8 @@ import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubComponent import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubItem import bruhcollective.itaysonlab.jetispot.ui.hub.components.* +//TODO: FIX UNSUPPORTED ID IN LISTENING HISTORY - BOBBYESP + @Composable fun HubBinder ( item: HubItem, diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/GridMediumCard.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/GridMediumCard.kt index 5f69c6b8..607e8701 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/GridMediumCard.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/GridMediumCard.kt @@ -19,26 +19,57 @@ import bruhcollective.itaysonlab.jetispot.ui.shared.Subtext @Composable fun GridMediumCard( - item: HubItem + item: HubItem ) { - val size = 160.dp - - Column(Modifier.fillMaxWidth().clickableHub(item)) { - var drawnTitle = false + val size = 160.dp - PreviewableAsyncImage(imageUrl = item.images?.main?.uri, placeholderType = item.images?.main?.placeholder, modifier = Modifier.size(size).clip( - RoundedCornerShape(if (item.images?.main?.isRounded == true) 12.dp else 0.dp) - ).align(Alignment.CenterHorizontally)) + Column( + Modifier + .fillMaxWidth() + .clickableHub(item)) { + var drawnTitle = false - if (!item.text?.title.isNullOrEmpty()) { - drawnTitle = true - MediumText(item.text!!.title!!, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth().padding(top = 8.dp).padding(horizontal = 16.dp)) - } + PreviewableAsyncImage( + imageUrl = item.images?.main?.uri, + placeholderType = item.images?.main?.placeholder, + modifier = Modifier + .size(size) + .clip( + RoundedCornerShape(if (item.images?.main?.isRounded == true) 12.dp else 0.dp) + ) + .align(Alignment.CenterHorizontally) + ) + + if (!item.text?.title.isNullOrEmpty()) { + drawnTitle = true + MediumText( + item.text!!.title!!, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp) + .padding(horizontal = 16.dp) + ) + } - if (!item.text?.subtitle.isNullOrEmpty()) { - Subtext(item.text!!.subtitle!!, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth().padding(top = if (drawnTitle) 2.dp else 8.dp, bottom = 12.dp).padding(horizontal = 16.dp)) - } else if (!item.text?.description.isNullOrEmpty()) { - Subtext(item.text!!.description!!, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth().padding(top = if (drawnTitle) 2.dp else 8.dp, bottom = 12.dp).padding(horizontal = 16.dp)) + if (!item.text?.subtitle.isNullOrEmpty()) { + Subtext( + item.text!!.subtitle!!, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(top = if (drawnTitle) 2.dp else 8.dp, bottom = 12.dp) + .padding(horizontal = 16.dp) + ) + } else if (!item.text?.description.isNullOrEmpty()) { + Subtext( + item.text!!.description!!, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(top = if (drawnTitle) 2.dp else 8.dp, bottom = 12.dp) + .padding(horizontal = 16.dp) + ) + } } - } } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ShortcutsCard.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ShortcutsCard.kt index 5f3377c5..7f2b5998 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ShortcutsCard.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ShortcutsCard.kt @@ -18,12 +18,34 @@ import bruhcollective.itaysonlab.jetispot.ui.shared.PreviewableAsyncImage @Composable fun ShortcutsCard( - item: HubItem + item: HubItem ) { - Card(colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.compositeSurfaceElevation(3.dp)), modifier = Modifier.height(56.dp).fillMaxWidth()) { - Row(Modifier.clickableHub(item)) { - PreviewableAsyncImage(imageUrl = item.images?.main?.uri, placeholderType = item.images?.main?.placeholder, modifier = Modifier.size(56.dp)) - Text(item.text!!.title!!, fontSize = 13.sp, lineHeight = 18.sp, maxLines = 2, overflow = TextOverflow.Ellipsis, modifier = Modifier.align(Alignment.CenterVertically).padding(horizontal = 8.dp)) + Card( + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.compositeSurfaceElevation( + 3.dp + ) + ), modifier = Modifier + .height(56.dp) + .fillMaxWidth() + ) { + Row(Modifier.clickableHub(item)) { + PreviewableAsyncImage( + imageUrl = item.images?.main?.uri, + placeholderType = item.images?.main?.placeholder, + modifier = Modifier.size(56.dp) + ) + Text( + item.text!!.title!!, + fontSize = 13.sp, + lineHeight = 18.sp, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + modifier = Modifier + .align(Alignment.CenterVertically) + .padding(horizontal = 8.dp) + .fillMaxWidth() + ) + } } - } } \ No newline at end of file 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 46d4d0ef..23173954 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 @@ -1,5 +1,6 @@ package bruhcollective.itaysonlab.jetispot.ui.screens.hub +import android.util.Log import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.graphics.Color @@ -41,6 +42,7 @@ class LikedSongsViewModel @Inject constructor( override suspend fun calculateDominantColor(url: String, dark: Boolean) = Color.Transparent suspend fun load(fullUri: String, id: String) = load { + Log.i("LikedSongsViewModel", "Loading liked songs; Full URI: $fullUri, ID: $id") val artistTracks = spCollectionManager.tracksByArtist(ArtistId.fromBase62(id).hexId()) HubResponse(body = buildList { 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 2016fafc..13fc712f 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 @@ -14,80 +14,82 @@ 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.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import bruhcollective.itaysonlab.jetispot.R import bruhcollective.itaysonlab.jetispot.core.SpPlayerServiceManager import bruhcollective.itaysonlab.jetispot.ui.shared.PlayPauseButton import bruhcollective.itaysonlab.jetispot.ui.shared.PreviewableSyncImage @Composable fun NowPlayingMiniplayer( - viewModel: NowPlayingViewModel, - modifier: Modifier, - visible: Boolean + viewModel: NowPlayingViewModel, + modifier: Modifier, + visible: Boolean ) { - AnimatedVisibility(visible = visible, enter = fadeIn(), exit = fadeOut()) { - Surface(tonalElevation = 8.dp, modifier = modifier) { - Box(Modifier.fillMaxSize()) { - LinearProgressIndicator( - progress = viewModel.currentPosition.value.progressRange, - color = MaterialTheme.colorScheme.primary, - modifier = Modifier - .height(2.dp) - .fillMaxWidth() - ) + AnimatedVisibility(visible = visible, enter = fadeIn(), exit = fadeOut()) { + Surface(tonalElevation = 8.dp, modifier = modifier) { + Box(Modifier.fillMaxSize()) { + LinearProgressIndicator( + progress = viewModel.currentPosition.value.progressRange, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier + .height(2.dp) + .fillMaxWidth() + ) - Row( - Modifier - .fillMaxHeight() - .padding(horizontal = 16.dp) - ) { - PreviewableSyncImage( - viewModel.currentTrack.value.artworkCompose, - placeholderType = "track", - modifier = Modifier - .size(48.dp) - .align(Alignment.CenterVertically) - .clip(RoundedCornerShape(8.dp)) - ) + Row( + Modifier + .fillMaxHeight() + .padding(horizontal = 16.dp) + ) { + PreviewableSyncImage( + viewModel.currentTrack.value.artworkCompose, + placeholderType = "track", + modifier = Modifier + .size(48.dp) + .align(Alignment.CenterVertically) + .clip(RoundedCornerShape(8.dp)) + ) - Column( - Modifier - .weight(2f) - .padding(horizontal = 14.dp) - .align(Alignment.CenterVertically) - ) { - Text( - viewModel.currentTrack.value.title, - color = MaterialTheme.colorScheme.onSurface, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - fontSize = 16.sp - ) - Text( - viewModel.currentTrack.value.artist, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - fontSize = 12.sp, - modifier = Modifier.padding(top = 2.dp) - ) - } + Column( + Modifier + .weight(2f) + .padding(horizontal = 14.dp) + .align(Alignment.CenterVertically) + ) { + Text( + if (viewModel.currentTrack.value.title == "Unknown Title") stringResource(id = R.string.unknown_title) else viewModel.currentTrack.value.title, + color = MaterialTheme.colorScheme.onSurface, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontSize = 16.sp + ) + Text( + if(viewModel.currentTrack.value.artist == "Unknown Artist") stringResource(id = R.string.unknown_artist) else viewModel.currentTrack.value.artist, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontSize = 12.sp, + modifier = Modifier.padding(top = 2.dp) + ) + } - PlayPauseButton( - viewModel.currentState.value == SpPlayerServiceManager.PlaybackState.Playing, - MaterialTheme.colorScheme.onSurface, - Modifier - .fillMaxHeight() - .width(56.dp) - .align(Alignment.CenterVertically) - .clickable { - viewModel.togglePlayPause() - } - ) + PlayPauseButton( + viewModel.currentState.value == SpPlayerServiceManager.PlaybackState.Playing, + MaterialTheme.colorScheme.onSurface, + Modifier + .fillMaxHeight() + .width(56.dp) + .align(Alignment.CenterVertically) + .clickable { + viewModel.togglePlayPause() + } + ) + } + } } - } } - } } \ No newline at end of file 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 966f9074..e3fc5363 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 @@ -15,9 +15,12 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.focus.focusTarget import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import bruhcollective.itaysonlab.jetispot.R +import bruhcollective.itaysonlab.jetispot.SpApp import bruhcollective.itaysonlab.jetispot.core.api.SpInternalApi import bruhcollective.itaysonlab.jetispot.proto.SearchEntity import bruhcollective.itaysonlab.jetispot.proto.SearchViewResponse @@ -59,7 +62,7 @@ fun SearchScreen( onValueChange = { viewModel.searchQuery = it }, placeholder = { if (viewModel.searchQuery.text.isEmpty()) { - Text(text = "What would you like to listen?") + Text(text = stringResource(id = R.string.search_placeholder)) } }, leadingIcon = { @@ -123,7 +126,11 @@ private fun SearchBinder( onClick: (SearchEntity.EntityCase, String) -> Unit ) { when { - response?.hitsCount == 0 -> PagingInfoPage(title = "Nothing found", text = "Correct your request and try again", modifier = Modifier.fillMaxSize()) + response?.hitsCount == 0 -> PagingInfoPage( + title = SpApp.context.getString(R.string.search_no_results), + text = SpApp.context.getString(R.string.search_no_results_desc), + modifier = Modifier.fillMaxSize() + ) response == null -> PagingLoadingPage(modifier = Modifier.fillMaxSize()) else -> { LazyColumn(Modifier.fillMaxSize()) { @@ -131,23 +138,23 @@ private fun SearchBinder( val text = remember(entity) { when (entity.entityCase) { SearchEntity.EntityCase.TRACK -> { - "Song • " + entity.track.trackArtistsList.joinToString { it.name } + SpApp.context.getString(R.string.song_prefix_with_dot) + entity.track.trackArtistsList.joinToString { it.name } } SearchEntity.EntityCase.PLAYLIST -> { when { - entity.playlist.personalized -> "Playlist • Personalized for you" - entity.playlist.ownedBySpotify -> "Playlist • By Spotify" - else -> "Playlist" + entity.playlist.personalized -> SpApp.context.getString(R.string.playlist_personalized_for_you) + entity.playlist.ownedBySpotify -> SpApp.context.getString(R.string.playlist_owned_by_spotify) + else -> SpApp.context.getString(R.string.playlist_prefix) } } SearchEntity.EntityCase.ALBUM -> { - "Album • " + entity.album.artistNamesList.joinToString() + SpApp.context.getString(R.string.album_with_dot)+ entity.album.artistNamesList.joinToString() } SearchEntity.EntityCase.ARTIST -> { - "Artist" + SpApp.context.getString(R.string.artist) } else -> "" diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/search/SearchViewModel.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/search/SearchViewModel.kt index 339d1dd8..5d0cf8b7 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/search/SearchViewModel.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/search/SearchViewModel.kt @@ -7,8 +7,8 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.lifecycle.ViewModel import bruhcollective.itaysonlab.jetispot.core.SpPlayerServiceManager import bruhcollective.itaysonlab.jetispot.core.api.SpInternalApi -import bruhcollective.itaysonlab.jetispot.core.objs.player.PfcContextData -import bruhcollective.itaysonlab.jetispot.core.objs.player.PlayFromContextPlayerData +import bruhcollective.itaysonlab.jetispot.core.objs.player.* +import bruhcollective.itaysonlab.jetispot.core.util.Log import bruhcollective.itaysonlab.jetispot.core.util.playCommand import bruhcollective.itaysonlab.jetispot.proto.SearchViewResponse import dagger.hilt.android.lifecycle.HiltViewModel @@ -21,6 +21,7 @@ class SearchViewModel @Inject constructor( private val spInternalApi: SpInternalApi, private val spPlayerServiceManager: SpPlayerServiceManager ): ViewModel(), CoroutineScope by MainScope() { + var searchQuery by mutableStateOf(TextFieldValue()) var searchResponse by mutableStateOf(null) @@ -34,11 +35,17 @@ class SearchViewModel @Inject constructor( } fun dispatchPlay(uri: String) { + Log.d("Dispatcher", "Dispatching play command for $uri") spPlayerServiceManager.play( - playCommand(uri) { - contextUri = "spotify:search:${searchQuery.text.replace(" ", "+")}" - } - ) + PlayFromContextData( + "spotify:search:${searchQuery.text.replace(" ", "+")}", + PlayFromContextPlayerData( + context = PfcContextData(url = "context://$uri", uri = uri), + state = PfcState(PfcStateOptions(shuffling_context = false)), + options = PfcOptions(player_options_override = PfcStateOptions(shuffling_context = false) ) + ) + + )) } fun clear() { diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 63fc41f2..20673d26 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -116,4 +116,7 @@ Miembro del plan Inglés Español + Título desconocido + Artista desconocido + Álbum desconocido \ 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 33163e71..97150eda 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -153,4 +153,16 @@ Spanish System default Your library + Unknown Title + Unknown Artist + Unknown Album + What would you like to listen? + "Song • " + Playlist • Personalized for you + Playlist • By Spotify + "Playlist" + "Album • " + Artist + Nothing was found + Correct your request and try again \ No newline at end of file From dccf766b7c05cd46a565ca0f72631a14c0b20e51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Fri, 2 Dec 2022 21:52:12 +0100 Subject: [PATCH 04/39] Added UpdateUtil (not implemented) and also added some Spanish strings --- app/build.gradle.kts | 6 +- .../jetispot/core/util/UpdateUtil.kt | 261 ++++++++++++++++++ .../itaysonlab/jetispot/ui/AppNavigation.kt | 6 + .../itaysonlab/jetispot/ui/screens/Screen.kt | 3 +- .../ui/screens/config/ConfigScreen.kt | 6 +- .../jetispot/ui/screens/hub/HubScreen.kt | 24 ++ app/src/main/res/values-es/strings.xml | 9 + 7 files changed, 309 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/UpdateUtil.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3823e6e4..6dff6042 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -13,8 +13,8 @@ plugins { apply(plugin = "dagger.hilt.android.plugin") val versionMajor = 0 -val versionMinor = 2 -val versionPatch = 1 +val versionMinor = 1 +val versionPatch = 0 val versionBuild = 0 val isStable = true @@ -150,6 +150,8 @@ moshi { dependencies { // Kotlin implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1") + // AndroidX coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.2.0") diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/UpdateUtil.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/UpdateUtil.kt new file mode 100644 index 00000000..4e7e4801 --- /dev/null +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/UpdateUtil.kt @@ -0,0 +1,261 @@ +package bruhcollective.itaysonlab.jetispot.core.util + +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Build +import android.util.Log +import androidx.compose.ui.res.stringResource +import androidx.core.content.FileProvider +import bruhcollective.itaysonlab.jetispot.R +import bruhcollective.itaysonlab.jetispot.SpApp +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.withContext +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import okhttp3.Call +import okhttp3.Callback +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import okhttp3.ResponseBody +import okio.IOException +import java.io.File +import java.util.regex.Pattern +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +object UpdateUtil { + private const val OWNER = "BobbyESP" + private const val REPO = "Jetispot" + private const val ARM64 = "arm64-v8a" + private const val ARM32 = "armeabi-v7a" + private const val X86 = "x86" + private const val X64 = "x86_64" + private const val TAG = "UpdateUtil" + + private val client = OkHttpClient() + private val requestForLatestRelease = + Request.Builder().url("https://api.github.com/repos/${OWNER}/${REPO}/releases/latest") + .build() + private val jsonFormat = Json { ignoreUnknownKeys = true } + + private suspend fun getLatestRelease(): LatestRelease { + return suspendCoroutine { continuation -> + client.newCall(requestForLatestRelease).enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + val responseData = response.body!!.string() + val latestRelease = jsonFormat.decodeFromString(responseData) + response.body!!.close() + continuation.resume(latestRelease) + } + + override fun onFailure(call: Call, e: IOException) { + continuation.resumeWithException(e) + } + }) + } + } + + suspend fun checkForUpdate(context: Context = SpApp.context): LatestRelease? { + val currentVersion = context.getCurrentVersion() + val latestRelease = getLatestRelease() + val latestVersion = Version(latestRelease.name ?: "") + return if (currentVersion < latestVersion) latestRelease + else null + } + + private fun Context.getCurrentVersion() = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + packageManager.getPackageInfo( + packageName, PackageManager.PackageInfoFlags.of(0) + ).versionName.toVersion() + } else { + packageManager.getPackageInfo( + packageName, 0 + ).versionName.toVersion() + } + + private fun String.toVersion() = Version(this) + + private fun Context.getLatestApk() = + File(getExternalFilesDir("apk"), "latest.apk") + + private fun Context.getFileProvider() = "${packageName}.provider" + + fun installLatestApk(context: Context = SpApp.context) = context.apply { + kotlin.runCatching { + val contentUri = FileProvider.getUriForFile(this, getFileProvider(), getLatestApk()) + val intent = Intent(Intent.ACTION_VIEW).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + setDataAndType(contentUri, "application/vnd.android.package-archive") + } + startActivity(intent) + }.onFailure { throwable: Throwable -> + throwable.printStackTrace() + + } + } + + suspend fun downloadApk( + context: Context = SpApp.context, + latestRelease: LatestRelease + ): Flow = withContext(Dispatchers.IO) { + + val apkVersion = context.packageManager.getPackageArchiveInfo( + context.getLatestApk().absolutePath, 0 + )?.versionName?.toVersion() ?: Version() + + Log.d(TAG, apkVersion.toString()) + + if (apkVersion >= Version(latestRelease.tagName.toString()) + ) { + return@withContext flow { emit(DownloadStatus.Finished(context.getLatestApk())) } + } + + val isArmArchSupported = Build.SUPPORTED_ABIS.contains(ARM32) + val is64BitsArchSupported = with(Build.SUPPORTED_ABIS) { + if (isArmArchSupported) contains(ARM64) + else contains(X64) + } + val preferredArch = if (is64BitsArchSupported) { + if (isArmArchSupported) ARM64 else X64 + } else { + if (isArmArchSupported) ARM32 else X86 + } + + val targetUrl = latestRelease.assets?.find { + return@find it.name?.contains(preferredArch) ?: false + }?.browserDownloadUrl ?: return@withContext emptyFlow() + + val request = Request.Builder().url(targetUrl).build() + try { + val response = client.newCall(request).execute() + val responseBody = response.body + return@withContext responseBody!!.downloadFileWithProgress(context.getLatestApk()) + } catch (e: Exception) { + e.printStackTrace() + } + emptyFlow() + } + + + private fun ResponseBody.downloadFileWithProgress(saveFile: File): Flow = flow { + emit(DownloadStatus.Progress(0)) + + var deleteFile = true + + try { + byteStream().use { inputStream -> + saveFile.outputStream().use { outputStream -> + val totalBytes = contentLength() + val data = ByteArray(8_192) + var progressBytes = 0L + + while (true) { + val bytes = inputStream.read(data) + + if (bytes == -1) { + break + } + + outputStream.channel + outputStream.write(data, 0, bytes) + progressBytes += bytes + emit(DownloadStatus.Progress(percent = ((progressBytes * 100) / totalBytes).toInt())) + } + + when { + progressBytes < totalBytes -> throw Exception("missing bytes") + progressBytes > totalBytes -> throw Exception("too many bytes") + else -> deleteFile = false + } + } + } + + emit(DownloadStatus.Finished(saveFile)) + } finally { + if (deleteFile) { + saveFile.delete() + } + } + }.flowOn(Dispatchers.IO).distinctUntilChanged() + + @Serializable + data class LatestRelease( + @SerialName("html_url") val htmlUrl: String? = null, + @SerialName("tag_name") val tagName: String? = null, + val name: String? = null, + val draft: Boolean? = null, + @SerialName("prerelease") val preRelease: Boolean? = null, + @SerialName("created_at") val createdAt: String? = null, + @SerialName("published_at") val publishedAt: String? = null, + val assets: List? = null, + val body: String? = null, + ) + + @Serializable + data class AssetsItem( + val name: String? = null, + @SerialName("content_type") val contentType: String? = null, + val size: Int? = null, + @SerialName("download_count") val downloadCount: Int? = null, + @SerialName("created_at") val createdAt: String? = null, + @SerialName("updated_at") val updatedAt: String? = null, + @SerialName("browser_download_url") val browserDownloadUrl: String? = null, + ) + + sealed class DownloadStatus { + object NotYet : DownloadStatus() + data class Progress(val percent: Int) : DownloadStatus() + data class Finished(val file: File) : DownloadStatus() + } + + class Version( + versionName: String = "v0.0.0", + ) { + var major: Int = 0 + private set + var minor: Int = 0 + private set + var patch: Int = 0 + private set + var build: Int = 0 + private set + + private fun toNumber(): Long { + return major * MAJOR + minor * MINOR + patch * PATCH + build * BUILD + } + + companion object { + private val pattern = Pattern.compile("""v?(\d+)\.(\d+)\.(\d+)(-.*?(\d+))*""") + + private const val BUILD = 1L + private const val PATCH = 100L + private const val MINOR = 10_000L + private const val MAJOR = 1_000_000L + } + + init { + val matcher = pattern.matcher(versionName) + if (matcher.find()) { + major = matcher.group(1)?.toInt() ?: 0 + minor = matcher.group(2)?.toInt() ?: 0 + patch = matcher.group(3)?.toInt() ?: 0 + build = matcher.group(5)?.toInt() ?: 99 + // Prioritize stable versions + } + } + + operator fun compareTo(other: Version) = toNumber().compareTo(other.toNumber()) + } +} \ No newline at end of file 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 d061e359..e38dc3ce 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/AppNavigation.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/AppNavigation.kt @@ -104,6 +104,8 @@ fun AppNavigation( composable(Screen.Search.route) { SearchScreen() } composable(Screen.Library.route) { YourLibraryContainerScreen() } + //DIALOGS + dialog(Dialog.AuthDisclaimer.route) { AlertDialog(onDismissRequest = { navController.popBackStack() }, icon = { Icon(Icons.Rounded.Warning, null) @@ -140,6 +142,10 @@ fun AppNavigation( }) } + dialog(Dialog.UpdateAvailable.route){ + //TODO: implement update dialog + } + bottomSheet(BottomSheet.JumpToArtist.route) { entry -> val data = remember { entry.arguments!!.getString("artistIdsAndRoles")!! } JumpToArtistBottomSheet(data = data) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/Screen.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/Screen.kt index af95998d..4766b916 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/Screen.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/Screen.kt @@ -47,7 +47,8 @@ enum class Dialog( val route: String ) { AuthDisclaimer("dialogs/disclaimers"), - Logout("dialogs/logout") + Logout("dialogs/logout"), + UpdateAvailable("dialogs/updateAvailable") } @Immutable 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 88d469cb..883a6558 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 @@ -109,12 +109,12 @@ class ConfigScreenViewModel @Inject constructor( }, {})) add(ConfigItem.Preference(R.string.about_sources, { ctx, _ -> "" }, { - it.openInBrowser("https://github.com/itaysonlab/jetispot") + it.openInBrowser("https://github.com/BobbyESP/Jetispot") })) - add(ConfigItem.Preference(R.string.about_channel, { ctx, _ -> "" }, { + /*add(ConfigItem.Preference(R.string.about_channel, { ctx, _ -> "" }, { it.openInBrowser("https://t.me/bruhcollective") - })) + }))*/ } override fun isRoot() = false 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 efa697c9..c30e1769 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 @@ -6,6 +6,7 @@ import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp @@ -17,12 +18,16 @@ import bruhcollective.itaysonlab.jetispot.core.collection.SpCollectionManager import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubResponse import bruhcollective.itaysonlab.jetispot.core.objs.hub.isGrid import bruhcollective.itaysonlab.jetispot.core.objs.player.PlayFromContextData +import bruhcollective.itaysonlab.jetispot.core.util.UpdateUtil 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.screens.Dialog import bruhcollective.itaysonlab.jetispot.ui.shared.PagingErrorPage import bruhcollective.itaysonlab.jetispot.ui.shared.PagingLoadingPage import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import javax.inject.Inject @@ -35,11 +40,30 @@ fun HubScreen( onAppBarTitleChange: (String) -> Unit = {}, ) { val scope = rememberCoroutineScope() + var latestRelease by remember { mutableStateOf(UpdateUtil.LatestRelease()) } + var showUpdateDialog by rememberSaveable { mutableStateOf(false) } + + val navController = LocalNavigationController.current viewModel.needContentPadding = needContentPadding LaunchedEffect(Unit) { viewModel.load(onAppBarTitleChange, loader) + launch(Dispatchers.IO) { + kotlin.runCatching { + val temp = UpdateUtil.checkForUpdate() + if (temp != null) { + latestRelease = temp + showUpdateDialog = true + } + }.onFailure { + it.printStackTrace() + } + } + } + + if(showUpdateDialog) { + navController.navigate(Dialog.UpdateAvailable) } when (viewModel.state) { diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 20673d26..d44d1848 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -119,4 +119,13 @@ Título desconocido Artista desconocido Álbum desconocido + Que te gustaría escuchar? + "Canción • " + Lista de reproducción • Personalizada para tí + Lista de reproducción • Hecha por Spotify + "Álbum • " + Artista + No se han encontrado resultados + Cambia tu criterio de búsqueda y vuelve a intentarlo + Lista de reproducción \ No newline at end of file From ac559f9d7960df7c0a8950b942ad009bc4f091b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Fri, 2 Dec 2022 22:25:36 +0100 Subject: [PATCH 05/39] Changed build naming --- app/build.gradle.kts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6dff6042..a9513575 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -124,6 +124,17 @@ android { kotlinCompilerExtensionVersion = compose_compiler_version } + applicationVariants.all { + outputs.all { + (this as com.android.build.gradle.internal.api.BaseVariantOutputImpl).outputFileName = + "Spowlo-${defaultConfig.versionName}-${name}.apk" + } + } + + lint { + disable.addAll(listOf("MissingTranslation", "ExtraTranslation")) + } + packagingOptions { resources { excludes += "/META-INF/*.kotlin_module" From 8d56cd257e15693d51857b525761984804008914 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Sat, 3 Dec 2022 01:22:43 +0100 Subject: [PATCH 06/39] Fixed app crashing --- app/build.gradle.kts | 6 +-- .../ui/blocks/TwoColumnAndImageBlock.kt | 40 ++++++++++++------- .../jetispot/ui/screens/hub/HubScreen.kt | 4 +- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a9513575..164a83df 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -14,7 +14,7 @@ apply(plugin = "dagger.hilt.android.plugin") val versionMajor = 0 val versionMinor = 1 -val versionPatch = 0 +val versionPatch = 1 val versionBuild = 0 val isStable = true @@ -87,7 +87,7 @@ android { buildTypes { release { isMinifyEnabled = true - isShrinkResources = true + isShrinkResources = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" @@ -127,7 +127,7 @@ android { applicationVariants.all { outputs.all { (this as com.android.build.gradle.internal.api.BaseVariantOutputImpl).outputFileName = - "Spowlo-${defaultConfig.versionName}-${name}.apk" + "Jetispot-${defaultConfig.versionName}-${name}.apk" } } diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/blocks/TwoColumnAndImageBlock.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/blocks/TwoColumnAndImageBlock.kt index b858de69..e7eb9a6d 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/blocks/TwoColumnAndImageBlock.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/blocks/TwoColumnAndImageBlock.kt @@ -20,28 +20,38 @@ fun TwoColumnAndImageBlock( text: String, modifier: Modifier = Modifier ) { + Row( modifier .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 12.dp)) { - PreviewableAsyncImage( - imageUrl = remember(artworkUri) { - artworkUri?.let { - "https://i.scdn.co/image/" + ImageId.fromUri( - if (artworkUri.startsWith( - "spotify:mosaic" - ) - ) "spotify:image:ab67706c0000bebb8d0ce13d55f634e290f744ba" else it - ).hexId() - } - }, placeholderType = "track", modifier = Modifier - .size(48.dp) - ) + .padding(horizontal = 16.dp, vertical = 12.dp) + ) { + if (artworkUri!!.startsWith("spotify:image")) { + val imageId = remember { artworkUri } + PreviewableAsyncImage( + imageUrl = remember(artworkUri) { + artworkUri.let { + "https://i.scdn.co/image/" + ImageId.fromUri( + imageId + ).hexId() + } + }, placeholderType = "track", modifier = Modifier + .size(48.dp) + ) + } else { + PreviewableAsyncImage( + imageUrl = artworkUri, + modifier = Modifier + .size(48.dp), + placeholderType = "track" + ) + } Column( Modifier .padding(horizontal = 12.dp) - .align(Alignment.CenterVertically)) { + .align(Alignment.CenterVertically) + ) { MediumText(title, fontWeight = FontWeight.Normal, fontSize = 18.sp) Spacer(Modifier.height(4.dp)) Subtext(text, modifier = Modifier, maxLines = 1) 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 c30e1769..bd62f483 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 @@ -49,7 +49,7 @@ fun HubScreen( LaunchedEffect(Unit) { viewModel.load(onAppBarTitleChange, loader) - launch(Dispatchers.IO) { + /*launch(Dispatchers.IO) { kotlin.runCatching { val temp = UpdateUtil.checkForUpdate() if (temp != null) { @@ -59,7 +59,7 @@ fun HubScreen( }.onFailure { it.printStackTrace() } - } + }*/ } if(showUpdateDialog) { From 6384363d0e9459ec2705da4fa21996c94807dde5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Sat, 3 Dec 2022 12:53:31 +0100 Subject: [PATCH 07/39] Added some strings and also added search header --- .../jetispot/core/SpSessionManager.kt | 24 ++++++++++++------- .../jetispot/core/api/SpInternalApi.kt | 2 +- .../core/collection/SpCollectionManager.kt | 3 ++- .../screens/nowplaying/NowPlayingViewModel.kt | 4 +++- .../ui/screens/search/SearchViewModel.kt | 2 +- app/src/main/res/values/strings.xml | 1 + 6 files changed, 24 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/SpSessionManager.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/SpSessionManager.kt index 562be965..3a7a5083 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/SpSessionManager.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/SpSessionManager.kt @@ -13,15 +13,23 @@ import javax.inject.Singleton @Suppress("BlockingMethodInNonBlockingContext") @Singleton class SpSessionManager @Inject constructor( - @ApplicationContext val appContext: Context, + @ApplicationContext val appContext: Context, ) { - private var _session: Session? = null - val session get() = _session ?: throw IllegalStateException("Session is not created yet!") + private var _session: Session? = null + val session get() = _session ?: throw IllegalStateException("Session is not created yet!") - fun createSession(): Session.Builder = Session.Builder(createCfg()).setDeviceType(Connect.DeviceType.SMARTPHONE).setDeviceName( - SpUtils.getDeviceName(appContext)).setDeviceId(null).setPreferredLocale(Locale.getDefault().language) - private fun createCfg() = Session.Configuration.Builder().setCacheEnabled(true).setDoCacheCleanUp(true).setCacheDir(File(appContext.cacheDir, "spa_cache")).setStoredCredentialsFile(File(appContext.filesDir, "spa_creds")).build() + fun createSession(): Session.Builder = + Session.Builder(createCfg()).setDeviceType(Connect.DeviceType.SMARTPHONE).setDeviceName( + SpUtils.getDeviceName(appContext) + ).setDeviceId(null).setPreferredLocale(Locale.getDefault().language) - fun isSignedIn() = _session?.isValid == true - fun setSession(s: Session) { _session = s } + private fun createCfg() = + Session.Configuration.Builder().setCacheEnabled(true).setDoCacheCleanUp(true) + .setCacheDir(File(appContext.cacheDir, "spa_cache")) + .setStoredCredentialsFile(File(appContext.filesDir, "spa_creds")).build() + + fun isSignedIn() = _session?.isValid == true + fun setSession(s: Session) { + _session = s + } } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt index e8e4341c..d7c1ed36 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt @@ -18,7 +18,7 @@ import java.util.* // TODO: Leave as it right now, later separate into other interfaces interface SpInternalApi { @GET("/homeview/v1/home") - suspend fun getHomeView(@Query("is_car_connected") carConnected: Boolean): HubResponse + suspend fun getHomeView(@Query("is_car_connected") carConnected: Boolean, @Query("locale") locale: String? = ""): HubResponse @GET("/chartview/v5/overview/android") suspend fun getChartView(): HubResponse 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 73022ddf..fc5720d7 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 @@ -25,6 +25,7 @@ class SpCollectionManager @Inject constructor( private val dao: LocalCollectionDao, private val metadataRequester: SpMetadataRequester ): DealerClient.MessageListener { + // it's important to use queuing here private val scopeDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() private val scope = CoroutineScope(scopeDispatcher + SupervisorJob()) @@ -64,7 +65,7 @@ class SpCollectionManager @Inject constructor( } suspend fun performScanIfEmpty(of: String) { - Log.d("SpColManager", "Performing scan of $of (if empty)") + Log.d("SpCollectionManager", "Performing scan of $of (if empty)") dao.getCollection(of) ?: writer.performScan(of) } diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/NowPlayingViewModel.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/NowPlayingViewModel.kt index 7c2f5886..0b3aaeb7 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/NowPlayingViewModel.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/NowPlayingViewModel.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.IntSize import androidx.lifecycle.ViewModel import bruhcollective.itaysonlab.jetispot.R +import bruhcollective.itaysonlab.jetispot.SpApp import bruhcollective.itaysonlab.jetispot.core.SpPlayerServiceManager import bruhcollective.itaysonlab.jetispot.core.api.SpPartnersApi import bruhcollective.itaysonlab.jetispot.core.lyrics.SpLyricsController @@ -113,13 +114,14 @@ class NowPlayingViewModel @Inject constructor( "playlist" -> R.string.playing_src_playlist "album" -> R.string.playing_src_album "artist" -> R.string.playing_src_artist + "search" -> R.string.playing_src_search else -> R.string.playing_src_unknown } } fun getHeaderText(): String { return when { - currentContextUri.value.contains("collection") -> "Liked Songs" // TODO: to R.string + currentContextUri.value.contains("collection") -> SpApp.context.getString(R.string.liked_songs) // TODO: to R.string else -> currentContext.value } } diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/search/SearchViewModel.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/search/SearchViewModel.kt index 5d0cf8b7..035ab552 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/search/SearchViewModel.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/search/SearchViewModel.kt @@ -42,7 +42,7 @@ class SearchViewModel @Inject constructor( PlayFromContextPlayerData( context = PfcContextData(url = "context://$uri", uri = uri), state = PfcState(PfcStateOptions(shuffling_context = false)), - options = PfcOptions(player_options_override = PfcStateOptions(shuffling_context = false) ) + options = PfcOptions(player_options_override = PfcStateOptions(shuffling_context = false)) ) )) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 97150eda..ef4e4685 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -165,4 +165,5 @@ Artist Nothing was found Correct your request and try again + playing from search \ No newline at end of file From 93b1c90d7ed6e5078b05f2d461a15223857b0e9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Sat, 3 Dec 2022 15:05:53 +0100 Subject: [PATCH 08/39] Added logs in some parts of the code. --- .../jetispot/core/api/SpInternalApi.kt | 2 ++ .../itaysonlab/jetispot/ui/AppNavigation.kt | 2 ++ .../jetispot/ui/screens/hub/HubScreen.kt | 25 ++++++++++--------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt index d7c1ed36..085a9e3c 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt @@ -1,5 +1,6 @@ package bruhcollective.itaysonlab.jetispot.core.api +import android.util.Log import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubResponse import bruhcollective.itaysonlab.jetispot.core.objs.playlists.LikedSongsResponse import bruhcollective.itaysonlab.jetispot.core.objs.tags.ContentFilterResponse @@ -100,6 +101,7 @@ interface SpInternalApi { appName = "ANDROID_MUSIC_APP" version = SpUtils.SPOTIFY_APP_VERSION }.build() + Log.d("SpInternalApi", "buildDacRequestForHome: $this") }.build() } } \ No newline at end of file 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 e38dc3ce..ecde5ddb 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/AppNavigation.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/AppNavigation.kt @@ -1,6 +1,7 @@ package bruhcollective.itaysonlab.jetispot.ui import android.content.Intent +import android.util.Log import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size @@ -72,6 +73,7 @@ fun AppNavigation( DacRendererScreen("", true, { getDacHome(SpInternalApi.buildDacRequestForHome(it)) }) + Log.i("AppNavigation", "Feed loaded") } composable(Screen.SpotifyIdRedirect.route, deepLinks = listOf(navDeepLink { 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 bd62f483..14e67d20 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 @@ -49,17 +49,6 @@ fun HubScreen( LaunchedEffect(Unit) { viewModel.load(onAppBarTitleChange, loader) - /*launch(Dispatchers.IO) { - kotlin.runCatching { - val temp = UpdateUtil.checkForUpdate() - if (temp != null) { - latestRelease = temp - showUpdateDialog = true - } - }.onFailure { - it.printStackTrace() - } - }*/ } if(showUpdateDialog) { @@ -164,4 +153,16 @@ class HubScreenViewModel @Inject constructor( class Error(val error: Exception) : State() object Loading : State() } -} \ No newline at end of file +} + +/*launch(Dispatchers.IO) { + kotlin.runCatching { + val temp = UpdateUtil.checkForUpdate() + if (temp != null) { + latestRelease = temp + showUpdateDialog = true + } + }.onFailure { + it.printStackTrace() + } +}*/ \ No newline at end of file From d02dfd7d664edde07a461aff5e33d1d0b29cd047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Sat, 3 Dec 2022 21:03:35 +0100 Subject: [PATCH 09/39] Added releases folder to the gitignore file --- .gitignore | 6 +----- .../bruhcollective/itaysonlab/jetispot/ui/dac/DacRender.kt | 3 +++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index f9f6498d..dc81aca2 100644 --- a/.gitignore +++ b/.gitignore @@ -14,9 +14,5 @@ .cxx local.properties /.idea/.name -/app/release/app-arm64-v8a-release.apk -/app/release/app-armeabi-v7a-release.apk -/app/release/app-x86-release.apk -/app/release/app-x86_64-release.apk +/app/release/ /jetispotkeypass.jks -/app/release/output-metadata.json 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 8429d12a..0686af02 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 @@ -56,6 +56,9 @@ fun DacRender ( is SectionComponent -> SectionComponentBinder(item) is RecentlyPlayedSectionComponent -> RecentlyPlayedSectionComponentBinder() + //Podcasts + //EpisodeCardActionsMediumComponent -> + // is SnappyGridSectionComponent -> SnappyGridSectionComponentBinder(item) // Other From bffd07f1cd71d01a3ee4f306e427b40bf37e1194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Sat, 3 Dec 2022 23:26:16 +0100 Subject: [PATCH 10/39] Formatted code in some files --- .../jetispot/ui/hub/components/AlbumHeader.kt | 148 +++++---- .../ui/hub/components/AlbumTrackRow.kt | 28 +- .../ui/hub/components/ArtistPinnedItem.kt | 34 ++- .../ui/hub/components/ArtistTrackRow.kt | 53 ++-- .../jetispot/ui/hub/components/Carousel.kt | 36 ++- .../ui/hub/components/CollectionHeader.kt | 2 +- .../ui/hub/components/EpisodeListItem.kt | 192 +++++++----- .../jetispot/ui/hub/components/FindCard.kt | 29 +- .../ui/hub/components/GridMediumCard.kt | 35 +-- .../ui/hub/components/HomeSectionHeader.kt | 20 +- .../hub/components/HomeSectionLargeHeader.kt | 30 +- .../jetispot/ui/hub/components/ImageRow.kt | 27 +- .../jetispot/ui/hub/components/LargerRow.kt | 34 ++- .../ui/hub/components/LikedSongsRow.kt | 4 +- .../jetispot/ui/hub/components/MediumCard.kt | 49 +-- .../ui/hub/components/OutlineButton.kt | 21 +- .../ui/hub/components/PlaylistHeader.kt | 289 +++++++++--------- .../ui/hub/components/PodcastTopicsStrip.kt | 62 ++-- .../ui/hub/components/ShortcutsCard.kt | 10 +- .../ui/hub/components/ShortcutsContainer.kt | 2 + .../jetispot/ui/hub/components/ShowHeader.kt | 91 +++--- .../ui/hub/components/SingleFocusCard.kt | 45 ++- .../jetispot/ui/hub/components/TextRow.kt | 9 +- .../essentials/EntityActionStrip.kt | 102 ++++--- app/src/main/res/values/strings.xml | 3 + 25 files changed, 813 insertions(+), 542 deletions(-) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/AlbumHeader.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/AlbumHeader.kt index af33b73a..f72ed607 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/AlbumHeader.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/AlbumHeader.kt @@ -33,78 +33,100 @@ import kotlinx.coroutines.launch @Composable fun AlbumHeader( - item: HubItem + item: HubItem ) { - val darkTheme = isSystemInDarkTheme() - val dominantColor = remember { mutableStateOf(Color.Transparent) } - val dominantColorAsBg = animateColorAsState(dominantColor.value) - val delegate = LocalHubScreenDelegate.current + val darkTheme = isSystemInDarkTheme() + val dominantColor = remember { mutableStateOf(Color.Transparent) } + val dominantColorAsBg = animateColorAsState(dominantColor.value) + val delegate = LocalHubScreenDelegate.current - LaunchedEffect(Unit) { - launch { - if (dominantColor.value != Color.Transparent) return@launch - dominantColor.value = delegate.calculateDominantColor(item.images?.main?.uri.toString(), darkTheme) + LaunchedEffect(Unit) { + launch { + if (dominantColor.value != Color.Transparent) return@launch + dominantColor.value = + delegate.calculateDominantColor(item.images?.main?.uri.toString(), darkTheme) + } } - } - Column(modifier = Modifier - .fillMaxHeight() - .background( - brush = Brush.verticalGradient( - colors = listOf(dominantColorAsBg.value, Color.Transparent) - ) - ) - .padding(top = 16.dp) - .statusBarsPadding()) { + Column( + modifier = Modifier + .fillMaxHeight() + .background( + brush = Brush.verticalGradient( + colors = listOf(dominantColorAsBg.value, Color.Transparent) + ) + ) + .padding(top = 16.dp) + .statusBarsPadding() + ) { - PreviewableAsyncImage(item.images?.main?.uri, item.images?.main?.placeholder, modifier = Modifier - .size((LocalConfiguration.current.screenWidthDp * 0.7).dp) - .align(Alignment.CenterHorizontally) - .padding(bottom = 8.dp)) + PreviewableAsyncImage( + item.images?.main?.uri, item.images?.main?.placeholder, modifier = Modifier + .size((LocalConfiguration.current.screenWidthDp * 0.7).dp) + .align(Alignment.CenterHorizontally) + .padding(bottom = 8.dp) + ) - MediumText(text = item.text!!.title!!, fontSize = 21.sp, modifier = Modifier - .padding(horizontal = 16.dp) - .padding(top = 8.dp)) + MediumText( + text = item.text!!.title!!, fontSize = 21.sp, modifier = Modifier + .padding(horizontal = 16.dp) + .padding(top = 8.dp) + ) - if (item.metadata!!.album!!.artists.size == 1) { - // large - Row(modifier = Modifier - .navClickable(enableRipple = false) { navController -> - HubEventHandler.handle( - navController, - delegate, - HubEvent.NavigateToUri(NavigateUri(item.metadata.album!!.artists[0].uri!!)) - ) - } - .padding(horizontal = 16.dp) - .padding(vertical = 12.dp)) { - AsyncImage(model = item.metadata.album!!.artists.first().images!![0].uri, contentScale = ContentScale.Crop, contentDescription = null, modifier = Modifier - .clip(CircleShape) - .size(32.dp)) - MediumText(text = item.metadata.album.artists.first().name!!, fontSize = 13.sp, modifier = Modifier - .align(Alignment.CenterVertically) - .padding(start = 12.dp)) - } - } else { - Row(Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) { - item.metadata.album!!.artists.forEachIndexed { idx, artist -> - MediumText(text = artist.name!!, fontSize = 13.sp, modifier = Modifier.navClickable(enableRipple = false) { navController -> - HubEventHandler.handle( - navController, - delegate, - HubEvent.NavigateToUri(NavigateUri(artist.uri!!)) - ) - }) + if (item.metadata!!.album!!.artists.size == 1) { + // large + Row(modifier = Modifier + .navClickable(enableRipple = false) { navController -> + HubEventHandler.handle( + navController, + delegate, + HubEvent.NavigateToUri(NavigateUri(item.metadata.album!!.artists[0].uri!!)) + ) + } + .padding(horizontal = 16.dp) + .padding(vertical = 12.dp)) { + AsyncImage( + model = item.metadata.album!!.artists.first().images!![0].uri, + contentScale = ContentScale.Crop, + contentDescription = null, + modifier = Modifier + .clip(CircleShape) + .size(32.dp) + ) + MediumText( + text = item.metadata.album.artists.first().name!!, + fontSize = 13.sp, + modifier = Modifier + .align(Alignment.CenterVertically) + .padding(start = 12.dp) + ) + } + } else { + Row(Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) { + item.metadata.album!!.artists.forEachIndexed { idx, artist -> + MediumText( + text = artist.name!!, + fontSize = 13.sp, + modifier = Modifier.navClickable(enableRipple = false) { navController -> + HubEventHandler.handle( + navController, + delegate, + HubEvent.NavigateToUri(NavigateUri(artist.uri!!)) + ) + }) - if (idx != item.metadata.album.artists.lastIndex) { - MediumText(text = " • ", fontSize = 13.sp) - } + if (idx != item.metadata.album.artists.lastIndex) { + MediumText(text = " • ", fontSize = 13.sp) + } + } + } } - } - } - Subtext(text = "${item.metadata.album!!.type} • ${item.metadata.album.year}", modifier = Modifier.padding(horizontal = 16.dp)) + Subtext( + text = "${item.metadata.album!!.type} • ${item.metadata.album.year}", + modifier = Modifier.padding(horizontal = 16.dp) + ) - EntityActionStrip(delegate, item) - } + EntityActionStrip(delegate, item) + } } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/AlbumTrackRow.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/AlbumTrackRow.kt index c9f848c6..197ac6a2 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/AlbumTrackRow.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/AlbumTrackRow.kt @@ -14,18 +14,26 @@ import bruhcollective.itaysonlab.jetispot.ui.shared.Subtext @Composable fun AlbumTrackRow( - item: HubItem + item: HubItem ) { - Column(Modifier.clickableHub(item).fillMaxWidth().padding(horizontal = 16.dp, vertical = 12.dp)) { - var drawnTitle = false + Column( + Modifier + .clickableHub(item) + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 12.dp) + ) { + var drawnTitle = false - if (!item.text?.title.isNullOrEmpty()) { - drawnTitle = true - MediumText(item.text!!.title!!, fontWeight = FontWeight.Normal) - } + if (!item.text?.title.isNullOrEmpty()) { + drawnTitle = true + MediumText(item.text!!.title!!, fontWeight = FontWeight.Normal) + } - if (!item.text?.subtitle.isNullOrEmpty()) { - Subtext(item.text!!.subtitle!!, modifier = Modifier.padding(top = if (drawnTitle) 4.dp else 8.dp)) + if (!item.text?.subtitle.isNullOrEmpty()) { + Subtext( + item.text!!.subtitle!!, + modifier = Modifier.padding(top = if (drawnTitle) 4.dp else 8.dp) + ) + } } - } } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ArtistPinnedItem.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ArtistPinnedItem.kt index 45466834..1a79f487 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ArtistPinnedItem.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ArtistPinnedItem.kt @@ -16,17 +16,29 @@ import bruhcollective.itaysonlab.jetispot.ui.shared.PreviewableAsyncImage import bruhcollective.itaysonlab.jetispot.ui.shared.Subtext @Composable -fun ArtistPinnedItem ( - item: HubItem +fun ArtistPinnedItem( + item: HubItem ) { - Row( - Modifier - .clickableHub(item) - .padding(horizontal = 16.dp, vertical = 2.dp)) { - PreviewableAsyncImage(imageUrl = item.images?.main?.uri, placeholderType = item.images?.main?.placeholder, modifier = Modifier.size(72.dp).padding(vertical = 8.dp).padding(end = 16.dp)) - Column(Modifier.align(Alignment.CenterVertically)) { - MediumText(item.text!!.title!!, fontWeight = FontWeight.Normal, modifier = Modifier.padding(bottom = 4.dp)) - Subtext(item.text.subtitle!!) + Row( + Modifier + .clickableHub(item) + .padding(horizontal = 16.dp, vertical = 2.dp) + ) { + PreviewableAsyncImage( + imageUrl = item.images?.main?.uri, + placeholderType = item.images?.main?.placeholder, + modifier = Modifier + .size(72.dp) + .padding(vertical = 8.dp) + .padding(end = 16.dp) + ) + Column(Modifier.align(Alignment.CenterVertically)) { + MediumText( + item.text!!.title!!, + fontWeight = FontWeight.Normal, + modifier = Modifier.padding(bottom = 4.dp) + ) + Subtext(item.text.subtitle!!) + } } - } } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ArtistTrackRow.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ArtistTrackRow.kt index 33e16ced..90db5cc8 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ArtistTrackRow.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ArtistTrackRow.kt @@ -18,29 +18,44 @@ import bruhcollective.itaysonlab.jetispot.ui.shared.Subtext @Composable fun ArtistTrackRow( - item: HubItem + item: HubItem ) { - Row( - Modifier - .clickableHub(item) - .padding(horizontal = 16.dp, vertical = 12.dp)) { - Text(text = (item.custom!!["rowNumber"] as Double).toInt().toString(), modifier = Modifier - .align(Alignment.CenterVertically) - .padding(end = 16.dp)) + Row( + Modifier + .clickableHub(item) + .padding(horizontal = 16.dp, vertical = 12.dp) + ) { + Text( + text = (item.custom!!["rowNumber"] as Double).toInt().toString(), modifier = Modifier + .align(Alignment.CenterVertically) + .padding(end = 16.dp) + ) - PreviewableAsyncImage(imageUrl = item.images?.main?.uri, placeholderType = item.images?.main?.placeholder, modifier = Modifier.align(Alignment.CenterVertically).size(48.dp)) + PreviewableAsyncImage( + imageUrl = item.images?.main?.uri, + placeholderType = item.images?.main?.placeholder, + modifier = Modifier + .align(Alignment.CenterVertically) + .size(48.dp) + ) - Column(Modifier.align(Alignment.CenterVertically).padding(start = 16.dp)) { - var drawnTitle = false + Column( + Modifier + .align(Alignment.CenterVertically) + .padding(start = 16.dp)) { + var drawnTitle = false - if (!item.text?.title.isNullOrEmpty()) { - drawnTitle = true - MediumText(item.text!!.title!!, fontWeight = FontWeight.Normal) - } + if (!item.text?.title.isNullOrEmpty()) { + drawnTitle = true + MediumText(item.text!!.title!!, fontWeight = FontWeight.Normal) + } - if (!item.text?.subtitle.isNullOrEmpty()) { - Subtext(item.text!!.subtitle!!, modifier = Modifier.padding(top = if (drawnTitle) 4.dp else 8.dp)) - } + if (!item.text?.subtitle.isNullOrEmpty()) { + Subtext( + item.text!!.subtitle!!, + modifier = Modifier.padding(top = if (drawnTitle) 4.dp else 8.dp) + ) + } + } } - } } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/Carousel.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/Carousel.kt index 30f30d0d..4fad3b5e 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/Carousel.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/Carousel.kt @@ -18,20 +18,30 @@ import bruhcollective.itaysonlab.jetispot.ui.hub.LocalHubScreenDelegate @Composable fun Carousel( - item: HubItem, + item: HubItem, ) { - val isSurroundedWithPadding = LocalHubScreenDelegate.current.isSurroundedWithPadding() - - Column(Modifier.padding(vertical = if (isSurroundedWithPadding) 0.dp else 8.dp)) { - if (item.text != null) { - Text(text = item.text.title!!, color = MaterialTheme.colorScheme.onSurface, fontWeight = FontWeight.Bold, fontSize = 16.sp, - modifier = Modifier.padding(horizontal = if (isSurroundedWithPadding) 0.dp else 16.dp).padding(bottom = 12.dp)) - } + val isSurroundedWithPadding = LocalHubScreenDelegate.current.isSurroundedWithPadding() + + Column(Modifier.padding(vertical = if (isSurroundedWithPadding) 0.dp else 8.dp)) { + if (item.text != null) { + Text( + text = item.text.title!!, + color = MaterialTheme.colorScheme.onSurface, + fontWeight = FontWeight.Bold, + fontSize = 16.sp, + modifier = Modifier + .padding(horizontal = if (isSurroundedWithPadding) 0.dp else 16.dp) + .padding(bottom = 12.dp) + ) + } - LazyRow(horizontalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier.padding(horizontal = if (isSurroundedWithPadding) 0.dp else 16.dp)) { - items(item.children ?: listOf()) { cItem -> - HubBinder(cItem) - } + LazyRow( + horizontalArrangement = Arrangement.spacedBy(12.dp), + modifier = Modifier.padding(horizontal = if (isSurroundedWithPadding) 0.dp else 16.dp) + ) { + items(item.children ?: listOf()) { cItem -> + HubBinder(cItem) + } + } } - } } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/CollectionHeader.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/CollectionHeader.kt index 3fca9db2..8fcd5285 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/CollectionHeader.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/CollectionHeader.kt @@ -54,7 +54,7 @@ fun CollectionHeader( .padding(horizontal = 16.dp) .padding(top = 8.dp)) - Subtext(text = "${item.custom!!["count"]} songs", fontSize = 14.sp, modifier = Modifier + Subtext(text = "${item.custom!!["count"]}" + stringResource(id = R.string.songs), fontSize = 14.sp, modifier = Modifier .padding(horizontal = 16.dp) .padding(top = 2.dp)) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/EpisodeListItem.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/EpisodeListItem.kt index dafe7914..6e40218b 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/EpisodeListItem.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/EpisodeListItem.kt @@ -32,94 +32,134 @@ import java.text.DateFormat import java.util.* @Composable -fun EpisodeListItem ( - item: HubItem +fun EpisodeListItem( + item: HubItem ) { - val episode = remember { item.custom!!["episode"] as Metadata.Episode } - val imageUrl = remember { SpUtils.getImageUrl(episode.coverImage.imageList.first { it.size == Metadata.Image.Size.DEFAULT }.fileId) } - val formattedDuration = remember { DateUtils.formatElapsedTime(episode.duration / 1000L) } - val formattedPublishDate = remember { - DateFormat.getDateInstance().format(Calendar.getInstance().apply { - set(episode.publishTime.year, episode.publishTime.month, episode.publishTime.day, episode.publishTime.hour, episode.publishTime.minute) - }.time) - } - - Column( - Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 8.dp)) { - Row { - PreviewableAsyncImage(imageUrl = imageUrl, placeholderType = "podcast", modifier = Modifier - .size(56.dp) - .clip(RoundedCornerShape(8.dp))) - Text(text = episode.name, modifier = Modifier - .padding(start = 16.dp) - .align(Alignment.CenterVertically), color = MaterialTheme.colorScheme.onSurface, maxLines = 2, overflow = TextOverflow.Ellipsis) + val episode = remember { item.custom!!["episode"] as Metadata.Episode } + val imageUrl = + remember { SpUtils.getImageUrl(episode.coverImage.imageList.first { it.size == Metadata.Image.Size.DEFAULT }.fileId) } + val formattedDuration = remember { DateUtils.formatElapsedTime(episode.duration / 1000L) } + val formattedPublishDate = remember { + DateFormat.getDateInstance().format(Calendar.getInstance().apply { + set( + episode.publishTime.year, + episode.publishTime.month, + episode.publishTime.day, + episode.publishTime.hour, + episode.publishTime.minute + ) + }.time) } - Spacer(Modifier.height(16.dp)) + Column( + Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp) + ) { + Row { + PreviewableAsyncImage( + imageUrl = imageUrl, placeholderType = "podcast", modifier = Modifier + .size(56.dp) + .clip(RoundedCornerShape(8.dp)) + ) + Text( + text = episode.name, + modifier = Modifier + .padding(start = 16.dp) + .align(Alignment.CenterVertically), + color = MaterialTheme.colorScheme.onSurface, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } - Text(text = episode.description, color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), fontSize = 13.sp, maxLines = 2, overflow = TextOverflow.Ellipsis) + Spacer(Modifier.height(16.dp)) - Spacer(Modifier.height(16.dp)) + Text( + text = episode.description, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), + fontSize = 13.sp, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) - Row { - if (episode.explicit) { - Icon(Icons.Rounded.Explicit, tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), contentDescription = null, modifier = Modifier - .size(16.dp) - .align(Alignment.CenterVertically)) - Text(text = " • ", color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), fontSize = 13.sp) - } + Spacer(Modifier.height(16.dp)) - Text(text = "$formattedDuration • $formattedPublishDate", color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), fontSize = 13.sp) - } + Row { + if (episode.explicit) { + Icon( + Icons.Rounded.Explicit, + tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), + contentDescription = null, + modifier = Modifier + .size(16.dp) + .align(Alignment.CenterVertically) + ) + Text( + text = " • ", + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), + fontSize = 13.sp + ) + } - Spacer(Modifier.height(16.dp)) + Text( + text = "$formattedDuration • $formattedPublishDate", + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), + fontSize = 13.sp + ) + } - Row { - IconButton(onClick = { /*TODO*/ }, - Modifier - .offset(y = 2.dp) - .align(Alignment.CenterVertically) - .size(28.dp)) { - Icon(Icons.Rounded.AddCircle, null) - } + Spacer(Modifier.height(16.dp)) - Spacer(Modifier.width(16.dp)) + Row { + IconButton( + onClick = { /*TODO*/ }, + Modifier + .offset(y = 2.dp) + .align(Alignment.CenterVertically) + .size(28.dp) + ) { + Icon(Icons.Rounded.AddCircle, null) + } - IconButton(onClick = { /*TODO*/ }, - Modifier - .offset(y = 2.dp) - .align(Alignment.CenterVertically) - .size(28.dp)) { - Icon(Icons.Rounded.Share, null) - } + Spacer(Modifier.width(16.dp)) - Spacer(Modifier.weight(1f)) + IconButton( + onClick = { /*TODO*/ }, + Modifier + .offset(y = 2.dp) + .align(Alignment.CenterVertically) + .size(28.dp) + ) { + Icon(Icons.Rounded.Share, null) + } - Box( - Modifier - .clickableHub(item) - .size(28.dp) - .clip(CircleShape) - .background(MaterialTheme.colorScheme.primary) - ) { - Icon( - imageVector = Icons.Rounded.PlayArrow, - tint = MaterialTheme.colorScheme.onPrimary, - contentDescription = null, - modifier = Modifier - .size(24.dp) - .align(Alignment.Center) - ) - } - } + Spacer(Modifier.weight(1f)) - Box( - Modifier - .padding(top = 16.dp) - .background(MaterialTheme.colorScheme.compositeSurfaceElevation(8.dp)) - .fillMaxWidth() - .height(1.dp)) {} - } + Box( + Modifier + .clickableHub(item) + .size(28.dp) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.primary) + ) { + Icon( + imageVector = Icons.Rounded.PlayArrow, + tint = MaterialTheme.colorScheme.onPrimary, + contentDescription = null, + modifier = Modifier + .size(24.dp) + .align(Alignment.Center) + ) + } + } + + Box( + Modifier + .padding(top = 16.dp) + .background(MaterialTheme.colorScheme.compositeSurfaceElevation(8.dp)) + .fillMaxWidth() + .height(1.dp) + ) {} + } } \ No newline at end of file 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 3eacb245..e2ffc3fb 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 @@ -17,12 +17,29 @@ import bruhcollective.itaysonlab.jetispot.ui.shared.PreviewableAsyncImage @Composable fun FindCard( - item: HubItem + item: HubItem ) { - Card(modifier = Modifier.height(100.dp).fillMaxWidth().clickableHub(item)) { - Box { - PreviewableAsyncImage(imageUrl = item.images?.background?.uri, placeholderType = item.images?.background?.placeholder, modifier = Modifier.fillMaxSize()) - Text(item.text!!.title!!, color = Color.White, fontSize = 18.sp, fontWeight = FontWeight.Bold, maxLines = 2, overflow = TextOverflow.Ellipsis, modifier = Modifier.align(Alignment.TopStart).padding(12.dp)) + Card(modifier = Modifier + .height(100.dp) + .fillMaxWidth() + .clickableHub(item)) { + Box { + PreviewableAsyncImage( + imageUrl = item.images?.background?.uri, + placeholderType = item.images?.background?.placeholder, + modifier = Modifier.fillMaxSize() + ) + Text( + item.text!!.title!!, + color = Color.White, + fontSize = 18.sp, + fontWeight = FontWeight.Bold, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + modifier = Modifier + .align(Alignment.TopStart) + .padding(12.dp) + ) + } } - } } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/GridMediumCard.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/GridMediumCard.kt index 607e8701..e48c1931 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/GridMediumCard.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/GridMediumCard.kt @@ -24,20 +24,21 @@ fun GridMediumCard( val size = 160.dp Column( - Modifier - .fillMaxWidth() - .clickableHub(item)) { + Modifier + .fillMaxWidth() + .clickableHub(item) + ) { var drawnTitle = false PreviewableAsyncImage( imageUrl = item.images?.main?.uri, placeholderType = item.images?.main?.placeholder, modifier = Modifier - .size(size) - .clip( - RoundedCornerShape(if (item.images?.main?.isRounded == true) 12.dp else 0.dp) - ) - .align(Alignment.CenterHorizontally) + .size(size) + .clip( + RoundedCornerShape(if (item.images?.main?.isRounded == true) 12.dp else 0.dp) + ) + .align(Alignment.CenterHorizontally) ) if (!item.text?.title.isNullOrEmpty()) { @@ -46,9 +47,9 @@ fun GridMediumCard( item.text!!.title!!, textAlign = TextAlign.Center, modifier = Modifier - .fillMaxWidth() - .padding(top = 8.dp) - .padding(horizontal = 16.dp) + .fillMaxWidth() + .padding(top = 8.dp) + .padding(horizontal = 16.dp) ) } @@ -57,18 +58,18 @@ fun GridMediumCard( item.text!!.subtitle!!, textAlign = TextAlign.Center, modifier = Modifier - .fillMaxWidth() - .padding(top = if (drawnTitle) 2.dp else 8.dp, bottom = 12.dp) - .padding(horizontal = 16.dp) + .fillMaxWidth() + .padding(top = if (drawnTitle) 2.dp else 8.dp, bottom = 12.dp) + .padding(horizontal = 16.dp) ) } else if (!item.text?.description.isNullOrEmpty()) { Subtext( item.text!!.description!!, textAlign = TextAlign.Center, modifier = Modifier - .fillMaxWidth() - .padding(top = if (drawnTitle) 2.dp else 8.dp, bottom = 12.dp) - .padding(horizontal = 16.dp) + .fillMaxWidth() + .padding(top = if (drawnTitle) 2.dp else 8.dp, bottom = 12.dp) + .padding(horizontal = 16.dp) ) } } diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/HomeSectionHeader.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/HomeSectionHeader.kt index 1c9342db..0c60479f 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/HomeSectionHeader.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/HomeSectionHeader.kt @@ -15,10 +15,20 @@ import bruhcollective.itaysonlab.jetispot.ui.hub.HubScreenDelegate import bruhcollective.itaysonlab.jetispot.ui.hub.LocalHubScreenDelegate @Composable -fun HomeSectionHeader ( - text: HubText, +fun HomeSectionHeader( + text: HubText, ) { - Box(Modifier.padding(vertical = 8.dp).padding(horizontal = if (LocalHubScreenDelegate.current.isSurroundedWithPadding()) 0.dp else 16.dp)) { - Text(text = text.title!!, color = MaterialTheme.colorScheme.onSurface, fontWeight = FontWeight.Bold, fontSize = 21.sp, modifier = Modifier.align(Alignment.CenterStart)) - } + Box( + Modifier + .padding(vertical = 8.dp) + .padding(horizontal = if (LocalHubScreenDelegate.current.isSurroundedWithPadding()) 0.dp else 16.dp) + ) { + Text( + text = text.title!!, + color = MaterialTheme.colorScheme.onSurface, + fontWeight = FontWeight.Bold, + fontSize = 21.sp, + modifier = Modifier.align(Alignment.CenterStart) + ) + } } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/HomeSectionLargeHeader.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/HomeSectionLargeHeader.kt index ff1fa1fb..a417d86a 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/HomeSectionLargeHeader.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/HomeSectionLargeHeader.kt @@ -18,17 +18,27 @@ import bruhcollective.itaysonlab.jetispot.ui.shared.PreviewableAsyncImage import bruhcollective.itaysonlab.jetispot.ui.shared.SubtextOverline @Composable -fun HomeSectionLargeHeader ( - item: HubItem +fun HomeSectionLargeHeader( + item: HubItem ) { - Row(Modifier.padding(vertical = 8.dp).clickableHub(item)) { - PreviewableAsyncImage(imageUrl = item.images?.main?.uri, placeholderType = item.images?.main?.placeholder, modifier = Modifier - .size(48.dp) - .clip(CircleShape)) + Row( + Modifier + .padding(vertical = 8.dp) + .clickableHub(item)) { + PreviewableAsyncImage( + imageUrl = item.images?.main?.uri, + placeholderType = item.images?.main?.placeholder, + modifier = Modifier + .size(48.dp) + .clip(CircleShape) + ) - Column(Modifier.padding(horizontal = 12.dp).align(Alignment.CenterVertically)) { - SubtextOverline(item.text!!.subtitle!!.uppercase(), modifier = Modifier) - MediumText(item.text.title!!, modifier = Modifier.padding(top = 2.dp), fontSize = 21.sp) + Column( + Modifier + .padding(horizontal = 12.dp) + .align(Alignment.CenterVertically)) { + SubtextOverline(item.text!!.subtitle!!.uppercase(), modifier = Modifier) + MediumText(item.text.title!!, modifier = Modifier.padding(top = 2.dp), fontSize = 21.sp) + } } - } } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ImageRow.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ImageRow.kt index 63418e99..8756134a 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ImageRow.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ImageRow.kt @@ -16,15 +16,26 @@ import bruhcollective.itaysonlab.jetispot.ui.shared.PreviewableAsyncImage @Composable fun ImageRow( - item: HubItem + item: HubItem ) { - Row(Modifier.clickableHub(item).fillMaxWidth().padding(horizontal = 16.dp, vertical = 12.dp)) { - PreviewableAsyncImage(imageUrl = item.images?.main?.uri, placeholderType = item.images?.main?.placeholder, modifier = Modifier - .size(42.dp) - .clip(CircleShape)) + Row( + Modifier + .clickableHub(item) + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 12.dp)) { + PreviewableAsyncImage( + imageUrl = item.images?.main?.uri, + placeholderType = item.images?.main?.placeholder, + modifier = Modifier + .size(42.dp) + .clip(CircleShape) + ) - Column(Modifier.padding(horizontal = 12.dp).align(Alignment.CenterVertically)) { - MediumText(item.text!!.title!!, fontWeight = FontWeight.Normal, fontSize = 18.sp) + Column( + Modifier + .padding(horizontal = 12.dp) + .align(Alignment.CenterVertically)) { + MediumText(item.text!!.title!!, fontWeight = FontWeight.Normal, fontSize = 18.sp) + } } - } } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/LargerRow.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/LargerRow.kt index 60c910f6..6804ca0b 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/LargerRow.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/LargerRow.kt @@ -16,17 +16,29 @@ import bruhcollective.itaysonlab.jetispot.ui.shared.PreviewableAsyncImage import bruhcollective.itaysonlab.jetispot.ui.shared.Subtext @Composable -fun LargerRow ( - item: HubItem +fun LargerRow( + item: HubItem ) { - Row( - Modifier - .clickableHub(item) - .padding(horizontal = 16.dp, vertical = 2.dp)) { - PreviewableAsyncImage(imageUrl = item.images?.main?.uri, placeholderType = item.images?.main?.placeholder, modifier = Modifier.size(72.dp).padding(end = 16.dp).padding(vertical = 8.dp)) - Column(Modifier.align(Alignment.CenterVertically)) { - MediumText(item.text!!.title!!, fontWeight = FontWeight.Normal, modifier = Modifier.padding(bottom = 4.dp)) - Subtext(item.text.subtitle!!) + Row( + Modifier + .clickableHub(item) + .padding(horizontal = 16.dp, vertical = 2.dp) + ) { + PreviewableAsyncImage( + imageUrl = item.images?.main?.uri, + placeholderType = item.images?.main?.placeholder, + modifier = Modifier + .size(72.dp) + .padding(end = 16.dp) + .padding(vertical = 8.dp) + ) + Column(Modifier.align(Alignment.CenterVertically)) { + MediumText( + item.text!!.title!!, + fontWeight = FontWeight.Normal, + modifier = Modifier.padding(bottom = 4.dp) + ) + Subtext(item.text.subtitle!!) + } } - } } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/LikedSongsRow.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/LikedSongsRow.kt index 95687b93..ba55133a 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/LikedSongsRow.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/LikedSongsRow.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import bruhcollective.itaysonlab.jetispot.SpApp import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubEvent import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubItem import bruhcollective.itaysonlab.jetispot.ui.hub.LocalHubScreenDelegate @@ -24,6 +25,7 @@ import bruhcollective.itaysonlab.jetispot.ui.shared.MediumText import bruhcollective.itaysonlab.jetispot.ui.shared.PreviewableAsyncImage import bruhcollective.itaysonlab.jetispot.ui.shared.Subtext import kotlinx.coroutines.launch +import bruhcollective.itaysonlab.jetispot.R import xyz.gianlu.librespot.metadata.ArtistId @Composable @@ -36,7 +38,7 @@ fun LikedSongsRow( LaunchedEffect(Unit) { launch { val count = delegate.getLikedSongsCount(ArtistId.fromBase62((item.events!!.click as HubEvent.NavigateToUri).data.uri.split(":").last()).hexId()) - likedSongsInfo.value = if (count == 0) "" else "$count songs by ${item.metadata!!.artist!!.name}" + likedSongsInfo.value = if (count == 0) "" else "$count " + SpApp.context.getString(R.string.songs_by) + " ${item.metadata!!.artist!!.name}" } } diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/MediumCard.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/MediumCard.kt index 40518aca..afdf4813 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/MediumCard.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/MediumCard.kt @@ -17,26 +17,41 @@ import bruhcollective.itaysonlab.jetispot.ui.shared.Subtext @Composable fun MediumCard( - item: HubItem + item: HubItem ) { - val size = 160.dp - - Column(Modifier.width(size).clickableHub(item)) { - var drawnTitle = false + val size = 160.dp - PreviewableAsyncImage(imageUrl = item.images?.main?.uri, placeholderType = item.images?.main?.placeholder, modifier = Modifier.size(size).clip( - RoundedCornerShape(if (item.images?.main?.isRounded == true) 12.dp else 0.dp) - )) + Column( + Modifier + .width(size) + .clickableHub(item)) { + var drawnTitle = false - if (!item.text?.title.isNullOrEmpty()) { - drawnTitle = true - MediumText(item.text!!.title!!, modifier = Modifier.padding(top = 8.dp)) - } + PreviewableAsyncImage( + imageUrl = item.images?.main?.uri, + placeholderType = item.images?.main?.placeholder, + modifier = Modifier + .size(size) + .clip( + RoundedCornerShape(if (item.images?.main?.isRounded == true) 12.dp else 0.dp) + ) + ) + + if (!item.text?.title.isNullOrEmpty()) { + drawnTitle = true + MediumText(item.text!!.title!!, modifier = Modifier.padding(top = 8.dp)) + } - if (!item.text?.subtitle.isNullOrEmpty()) { - Subtext(item.text!!.subtitle!!, modifier = Modifier.padding(top = if (drawnTitle) 4.dp else 8.dp)) - } else if (!item.text?.description.isNullOrEmpty()) { - Subtext(item.text!!.description!!, modifier = Modifier.padding(top = if (drawnTitle) 4.dp else 8.dp)) + if (!item.text?.subtitle.isNullOrEmpty()) { + Subtext( + item.text!!.subtitle!!, + modifier = Modifier.padding(top = if (drawnTitle) 4.dp else 8.dp) + ) + } else if (!item.text?.description.isNullOrEmpty()) { + Subtext( + item.text!!.description!!, + modifier = Modifier.padding(top = if (drawnTitle) 4.dp else 8.dp) + ) + } } - } } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/OutlineButton.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/OutlineButton.kt index af740d37..a1502623 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/OutlineButton.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/OutlineButton.kt @@ -20,10 +20,25 @@ import bruhcollective.itaysonlab.jetispot.ui.shared.MediumText fun OutlineButton( item: HubItem ) { - Box(Modifier.clickableHub(item).padding(16.dp)) { + Box( + Modifier + .clickableHub(item) + .padding(16.dp) + ) { Row(Modifier.align(Alignment.Center)) { - MediumText(text = item.text?.title!!, modifier = Modifier.align(Alignment.CenterVertically)) - Icon(Icons.Rounded.ChevronRight, null, tint = MaterialTheme.colorScheme.onSurface, modifier = Modifier.padding(start = 2.dp).size(20.dp).align(Alignment.CenterVertically)) + MediumText( + text = item.text?.title!!, + modifier = Modifier.align(Alignment.CenterVertically) + ) + Icon( + Icons.Rounded.ChevronRight, + null, + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier + .padding(start = 2.dp) + .size(20.dp) + .align(Alignment.CenterVertically) + ) } } } \ No newline at end of file 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 342b04b2..86c35c41 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 @@ -23,8 +23,9 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import bruhcollective.itaysonlab.jetispot.SpApp import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubItem -import bruhcollective.itaysonlab.jetispot.ui.hub.HubScreenDelegate +import bruhcollective.itaysonlab.jetispot.R import bruhcollective.itaysonlab.jetispot.ui.hub.LocalHubScreenDelegate import bruhcollective.itaysonlab.jetispot.ui.hub.components.essentials.EntityActionStrip import bruhcollective.itaysonlab.jetispot.ui.shared.MediumText @@ -35,166 +36,168 @@ import kotlinx.coroutines.launch @Composable fun PlaylistHeader( - item: HubItem + item: HubItem ) { - val darkTheme = isSystemInDarkTheme() - val dominantColor = remember { mutableStateOf(Color.Transparent) } - val dominantColorAsBg = animateColorAsState(dominantColor.value) - val delegate = LocalHubScreenDelegate.current - - LaunchedEffect(Unit) { - launch { - if (dominantColor.value != Color.Transparent) return@launch - dominantColor.value = - delegate.calculateDominantColor(item.images?.main?.uri.toString(), darkTheme) + val darkTheme = isSystemInDarkTheme() + val dominantColor = remember { mutableStateOf(Color.Transparent) } + val dominantColorAsBg = animateColorAsState(dominantColor.value) + val delegate = LocalHubScreenDelegate.current + + LaunchedEffect(Unit) { + launch { + if (dominantColor.value != Color.Transparent) return@launch + dominantColor.value = + delegate.calculateDominantColor(item.images?.main?.uri.toString(), darkTheme) + } } - } - Column( - Modifier - .fillMaxHeight() - .fillMaxWidth() - .background( - brush = Brush.verticalGradient( - colors = listOf(dominantColorAsBg.value, Color.Transparent) + Column( + Modifier + .fillMaxHeight() + .fillMaxWidth() + .background( + brush = Brush.verticalGradient( + colors = listOf(dominantColorAsBg.value, Color.Transparent) + ) + ) + .padding(top = 16.dp) + .statusBarsPadding() + ) { + PreviewableAsyncImage( + item.images?.main?.uri, "playlist", modifier = Modifier + .size((LocalConfiguration.current.screenWidthDp * 0.7).dp) + .align(Alignment.CenterHorizontally) + .padding(bottom = 8.dp) + ) + + MediumText( + text = item.text?.title!!, fontSize = 21.sp, modifier = Modifier + .padding(horizontal = 16.dp) + .padding(top = 8.dp) ) - ) - .padding(top = 16.dp) - .statusBarsPadding() - ) { - PreviewableAsyncImage(item.images?.main?.uri, "playlist", modifier = Modifier - .size((LocalConfiguration.current.screenWidthDp * 0.7).dp) - .align(Alignment.CenterHorizontally) - .padding(bottom = 8.dp)) - - MediumText( - text = item.text?.title!!, fontSize = 21.sp, modifier = Modifier - .padding(horizontal = 16.dp) - .padding(top = 8.dp) - ) - - 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 - .padding(horizontal = 16.dp) - .padding(top = 8.dp) - ) - } - PlaylistHeaderAdditionalInfo(item.custom) - EntityActionStrip(delegate, item) - } + 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 + .padding(horizontal = 16.dp) + .padding(top = 8.dp) + ) + } + + PlaylistHeaderAdditionalInfo(item.custom) + EntityActionStrip(delegate, item) + } } @Composable fun LargePlaylistHeader( - item: HubItem + item: HubItem ) { - val delegate = LocalHubScreenDelegate.current + val delegate = LocalHubScreenDelegate.current + + Column { + Box( + Modifier + .fillMaxWidth() + .height(240.dp) + ) { + AsyncImage( + model = item.images?.main?.uri, + contentScale = ContentScale.Crop, + contentDescription = null, + modifier = Modifier + .fillMaxSize() + ) - Column { - Box( - Modifier - .fillMaxWidth() - .height(240.dp) - ) { - AsyncImage( - model = item.images?.main?.uri, - contentScale = ContentScale.Crop, - contentDescription = null, - modifier = Modifier - .fillMaxSize() - ) - - Box( - Modifier - .background( - brush = Brush.verticalGradient( - colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.7f)) + Box( + Modifier + .background( + brush = Brush.verticalGradient( + colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.7f)) + ) + ) + .fillMaxSize() ) - ) - .fillMaxSize() - ) - - MediumText( - text = item.text?.title!!, - fontSize = 48.sp, - lineHeight = 52.sp, - maxLines = 2, - modifier = Modifier - .align(Alignment.BottomStart) - .padding(horizontal = 16.dp) - .padding(bottom = 8.dp) - ) - } - 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 - .padding(horizontal = 16.dp) - .padding(top = 16.dp) - ) - } + MediumText( + text = item.text?.title!!, + fontSize = 48.sp, + lineHeight = 52.sp, + maxLines = 2, + modifier = Modifier + .align(Alignment.BottomStart) + .padding(horizontal = 16.dp) + .padding(bottom = 8.dp) + ) + } + + 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 + .padding(horizontal = 16.dp) + .padding(top = 16.dp) + ) + } - PlaylistHeaderAdditionalInfo(item.custom) - EntityActionStrip(delegate, item) - } + PlaylistHeaderAdditionalInfo(item.custom) + EntityActionStrip(delegate, item) + } } @Composable fun PlaylistHeaderAdditionalInfo( - custom: Map? + custom: Map? ) { - custom ?: return - - val ownerPic = remember(custom) { custom["owner_pic"] as String } - val ownerName = remember(custom) { custom["owner_name"] as String } - val likesCount = remember(custom) { custom["likes_count"] as Long } - val totalDuration = remember(custom) { custom["total_duration"] as String } - - Spacer(modifier = Modifier.height(12.dp)) - - Row(Modifier - .navClickable( - enableRipple = false - ) { navController -> navController.navigate(custom["owner_username"] as String) } - .fillMaxWidth() - .padding(horizontal = 16.dp)) { - PreviewableAsyncImage( - imageUrl = ownerPic, placeholderType = "user", modifier = Modifier - .clip(CircleShape) - .size(32.dp) - ) - MediumText( - text = ownerName, fontSize = 13.sp, modifier = Modifier - .align(Alignment.CenterVertically) - .padding(start = 12.dp) - ) - } - - Spacer(modifier = Modifier.height(12.dp)) - - Row( - Modifier + custom ?: return + + val ownerPic = remember(custom) { custom["owner_pic"] as String } + val ownerName = remember(custom) { custom["owner_name"] as String } + val likesCount = remember(custom) { custom["likes_count"] as Long } + val totalDuration = remember(custom) { custom["total_duration"] as String } + + Spacer(modifier = Modifier.height(12.dp)) + + Row(Modifier + .navClickable( + enableRipple = false + ) { navController -> navController.navigate(custom["owner_username"] as String) } .fillMaxWidth() - .padding(horizontal = 16.dp) - ) { - Icon(Icons.Rounded.Language, contentDescription = null, modifier = Modifier.size(26.dp)) - Text( - text = "$likesCount likes • $totalDuration", - fontSize = 12.sp, - maxLines = 1, - modifier = Modifier - .align(Alignment.CenterVertically) - .padding(start = 8.dp) - ) - } - - Spacer(modifier = Modifier.height(6.dp)) + .padding(horizontal = 16.dp)) { + PreviewableAsyncImage( + imageUrl = ownerPic, placeholderType = "user", modifier = Modifier + .clip(CircleShape) + .size(32.dp) + ) + MediumText( + text = ownerName, fontSize = 13.sp, modifier = Modifier + .align(Alignment.CenterVertically) + .padding(start = 12.dp) + ) + } + + Spacer(modifier = Modifier.height(12.dp)) + + Row( + Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + ) { + Icon(Icons.Rounded.Language, contentDescription = null, modifier = Modifier.size(26.dp)) + Text( + text = "$likesCount " + SpApp.context.getString(R.string.likes_dot) + totalDuration, + fontSize = 12.sp, + maxLines = 1, + modifier = Modifier + .align(Alignment.CenterVertically) + .padding(start = 8.dp) + ) + } + + Spacer(modifier = Modifier.height(6.dp)) } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/PodcastTopicsStrip.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/PodcastTopicsStrip.kt index 54e86f85..e20cafb6 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/PodcastTopicsStrip.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/PodcastTopicsStrip.kt @@ -21,39 +21,49 @@ import com.spotify.podcastextensions.proto.PodcastTopics @OptIn(ExperimentalMaterial3Api::class) @Composable -fun PodcastTopicsStrip ( - item: HubItem +fun PodcastTopicsStrip( + item: HubItem ) { - val navController = LocalNavigationController.current - val topics = remember { item.custom!!["topics"] as PodcastTopics } - val rating = remember { item.custom!!["ratings"] as PodcastRating } + val navController = LocalNavigationController.current + val topics = remember { item.custom!!["topics"] as PodcastTopics } + val rating = remember { item.custom!!["ratings"] as PodcastRating } - LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp), contentPadding = PaddingValues(horizontal = 16.dp)) { - item { - ElevatedSuggestionChip(onClick = { - // TODO - }, icon = { - Icon(imageVector = Icons.Rounded.Star, contentDescription = null) - }, label = { - Text(text = "${String.format("%.2f", rating.averageRating.average)} (${rating.averageRating.totalRatings})") - }) - } + LazyRow( + horizontalArrangement = Arrangement.spacedBy(8.dp), + contentPadding = PaddingValues(horizontal = 16.dp) + ) { + item { + ElevatedSuggestionChip(onClick = { + // TODO + }, icon = { + Icon(imageVector = Icons.Rounded.Star, contentDescription = null) + }, label = { + Text( + text = "${ + String.format( + "%.2f", + rating.averageRating.average + ) + } (${rating.averageRating.totalRatings})" + ) + }) + } - items(topics.topicsList) { topic -> - PodcastTopic(topic = topic, onClick = navController::navigate) + items(topics.topicsList) { topic -> + PodcastTopic(topic = topic, onClick = navController::navigate) + } } - } } @OptIn(ExperimentalMaterial3Api::class) @Composable -private fun PodcastTopic ( - topic: PodcastTopic, - onClick: (String) -> Unit +private fun PodcastTopic( + topic: PodcastTopic, + onClick: (String) -> Unit ) { - ElevatedSuggestionChip(onClick = { - onClick(topic.uri) - }, label = { - Text(text = topic.title) - }) + ElevatedSuggestionChip(onClick = { + onClick(topic.uri) + }, label = { + Text(text = topic.title) + }) } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ShortcutsCard.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ShortcutsCard.kt index 7f2b5998..13674220 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ShortcutsCard.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ShortcutsCard.kt @@ -26,8 +26,8 @@ fun ShortcutsCard( 3.dp ) ), modifier = Modifier - .height(56.dp) - .fillMaxWidth() + .height(56.dp) + .fillMaxWidth() ) { Row(Modifier.clickableHub(item)) { PreviewableAsyncImage( @@ -42,9 +42,9 @@ fun ShortcutsCard( maxLines = 2, overflow = TextOverflow.Ellipsis, modifier = Modifier - .align(Alignment.CenterVertically) - .padding(horizontal = 8.dp) - .fillMaxWidth() + .align(Alignment.CenterVertically) + .padding(horizontal = 8.dp) + .fillMaxWidth() ) } } diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ShortcutsContainer.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ShortcutsContainer.kt index 9333e4f7..b3262487 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ShortcutsContainer.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ShortcutsContainer.kt @@ -4,6 +4,8 @@ import androidx.compose.runtime.Composable import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubItem import bruhcollective.itaysonlab.jetispot.ui.hub.HubBinder +//Maybe Feed top shortcuts? + @Composable fun ShortcutsContainer ( children: List diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ShowHeader.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ShowHeader.kt index e15805a4..973d8fc3 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ShowHeader.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ShowHeader.kt @@ -21,53 +21,58 @@ import com.spotify.metadata.Metadata @Composable fun ShowHeader( - item: HubItem + item: HubItem ) { - val show = remember { item.custom!!["show"] as Metadata.Show } - val imageUrl = remember { SpUtils.getImageUrl(show.coverImage.imageList.first { it.size == Metadata.Image.Size.DEFAULT }.fileId) } + val show = remember { item.custom!!["show"] as Metadata.Show } + val imageUrl = + remember { SpUtils.getImageUrl(show.coverImage.imageList.first { it.size == Metadata.Image.Size.DEFAULT }.fileId) } - Column { - Box( - Modifier - .fillMaxWidth() - .height(240.dp) - ) { - AsyncImage( - model = imageUrl, - contentScale = ContentScale.Crop, - contentDescription = null, - modifier = Modifier - .fillMaxSize() - ) + Column { + Box( + Modifier + .fillMaxWidth() + .height(240.dp) + ) { + AsyncImage( + model = imageUrl, + contentScale = ContentScale.Crop, + contentDescription = null, + modifier = Modifier + .fillMaxSize() + ) - Box( - Modifier - .background( - brush = Brush.verticalGradient( - colors = listOf(Color.Transparent, MaterialTheme.colorScheme.surface.copy(alpha = 0.9f), MaterialTheme.colorScheme.surface) + Box( + Modifier + .background( + brush = Brush.verticalGradient( + colors = listOf( + Color.Transparent, + MaterialTheme.colorScheme.surface.copy(alpha = 0.9f), + MaterialTheme.colorScheme.surface + ) + ) + ) + .fillMaxSize() ) - ) - .fillMaxSize() - ) - MediumText( - text = show.name, - fontSize = 48.sp, - lineHeight = 52.sp, - maxLines = 2, - modifier = Modifier - .align(Alignment.BottomStart) - .padding(horizontal = 16.dp) - .padding(bottom = 8.dp) - ) - } + MediumText( + text = show.name, + fontSize = 48.sp, + lineHeight = 52.sp, + maxLines = 2, + modifier = Modifier + .align(Alignment.BottomStart) + .padding(horizontal = 16.dp) + .padding(bottom = 8.dp) + ) + } - Text( - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), - fontSize = 12.sp, - lineHeight = 18.sp, - text = show.description, modifier = Modifier - .padding(horizontal = 16.dp) - ) - } + Text( + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), + fontSize = 12.sp, + lineHeight = 18.sp, + text = show.description, modifier = Modifier + .padding(horizontal = 16.dp) + ) + } } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/SingleFocusCard.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/SingleFocusCard.kt index f73f07d8..4a1e2322 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/SingleFocusCard.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/SingleFocusCard.kt @@ -16,23 +16,36 @@ import bruhcollective.itaysonlab.jetispot.ui.shared.PreviewableAsyncImage import bruhcollective.itaysonlab.jetispot.ui.shared.Subtext @Composable -fun SingleFocusCard ( - item: HubItem +fun SingleFocusCard( + item: HubItem ) { - Card(colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.compositeSurfaceElevation(3.dp)), modifier = Modifier - .height(120.dp) - .fillMaxWidth() - .clickableHub(item)) { - Row { - PreviewableAsyncImage(imageUrl = item.images?.main?.uri, placeholderType = item.images?.main?.placeholder, modifier = Modifier - .fillMaxHeight() - .width(120.dp)) - Box(Modifier.fillMaxSize().padding(16.dp)) { - Column(Modifier.align(Alignment.TopStart)) { - MediumText(text = item.text!!.title!!) - Subtext(text = item.text.subtitle!!) + Card( + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.compositeSurfaceElevation( + 3.dp + ) + ), modifier = Modifier + .height(120.dp) + .fillMaxWidth() + .clickableHub(item) + ) { + Row { + PreviewableAsyncImage( + imageUrl = item.images?.main?.uri, + placeholderType = item.images?.main?.placeholder, + modifier = Modifier + .fillMaxHeight() + .width(120.dp) + ) + Box( + Modifier + .fillMaxSize() + .padding(16.dp)) { + Column(Modifier.align(Alignment.TopStart)) { + MediumText(text = item.text!!.title!!) + Subtext(text = item.text.subtitle!!) + } + } } - } } - } } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/TextRow.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/TextRow.kt index d45ea24c..13874451 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/TextRow.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/TextRow.kt @@ -11,7 +11,12 @@ import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubText @Composable fun TextRow( - text: HubText + text: HubText ) { - Text(text.title ?: text.description ?: "", color = MaterialTheme.colorScheme.onSurface.copy(alpha = if (text.description == null) 1f else 0.7f), fontSize = 16.sp, modifier = Modifier.padding(horizontal = 16.dp, vertical = 6.dp)) + Text( + text.title ?: text.description ?: "", + color = MaterialTheme.colorScheme.onSurface.copy(alpha = if (text.description == null) 1f else 0.7f), + fontSize = 16.sp, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 6.dp) + ) } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/essentials/EntityActionStrip.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/essentials/EntityActionStrip.kt index 4e568a09..3e2056c8 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/essentials/EntityActionStrip.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/essentials/EntityActionStrip.kt @@ -20,48 +20,78 @@ import bruhcollective.itaysonlab.jetispot.ui.hub.HubScreenDelegate import bruhcollective.itaysonlab.jetispot.ui.hub.clickableHub @Composable -fun EntityActionStrip ( - delegate: HubScreenDelegate, - item: HubItem +fun EntityActionStrip( + delegate: HubScreenDelegate, + item: HubItem ) { - Row(Modifier.padding(horizontal = 16.dp).padding(bottom = 4.dp)) { - IconButton(onClick = { delegate.toggleMainObjectAddedState() }, Modifier.offset(y = 2.dp).align(Alignment.CenterVertically).size(28.dp)) { - Icon(if (delegate.getMainObjectAddedState().value) Icons.Rounded.Favorite else Icons.Rounded.FavoriteBorder, null) - } + Row( + Modifier + .padding(horizontal = 16.dp) + .padding(bottom = 4.dp)) { + IconButton( + onClick = { delegate.toggleMainObjectAddedState() }, + Modifier + .offset(y = 2.dp) + .align(Alignment.CenterVertically) + .size(28.dp) + ) { + Icon( + if (delegate.getMainObjectAddedState().value) Icons.Rounded.Favorite else Icons.Rounded.FavoriteBorder, + null + ) + } - Spacer(Modifier.width(16.dp)) + Spacer(Modifier.width(16.dp)) - IconButton(onClick = { /*TODO*/ }, Modifier.offset(y = 2.dp).align(Alignment.CenterVertically).size(28.dp)) { - Icon(Icons.Rounded.MoreVert, null) - } + IconButton( + onClick = { /*TODO*/ }, + Modifier + .offset(y = 2.dp) + .align(Alignment.CenterVertically) + .size(28.dp) + ) { + Icon(Icons.Rounded.MoreVert, null) + } - Spacer(Modifier.weight(1f)) + Spacer(Modifier.weight(1f)) - Box(Modifier.size(48.dp)) { - Box( - Modifier.clip(CircleShape).size(48.dp).background(MaterialTheme.colorScheme.primary).clickableHub(item.children!![0]) - ) { - Icon( - imageVector = Icons.Rounded.PlayArrow, - tint = MaterialTheme.colorScheme.onPrimary, - contentDescription = null, - modifier = Modifier.size(32.dp).align(Alignment.Center) - ) - } + Box(Modifier.size(48.dp)) { + Box( + Modifier + .clip(CircleShape) + .size(48.dp) + .background(MaterialTheme.colorScheme.primary) + .clickableHub(item.children!![0]) + ) { + Icon( + imageVector = Icons.Rounded.PlayArrow, + tint = MaterialTheme.colorScheme.onPrimary, + contentDescription = null, + modifier = Modifier + .size(32.dp) + .align(Alignment.Center) + ) + } - if ((item.children[0].events?.click as? HubEvent.PlayFromContext)?.data?.player?.options?.player_options_override?.shuffling_context != false) { - Box( - Modifier.align(Alignment.BottomEnd).offset(4.dp, 4.dp).clip(CircleShape).size(22.dp) - .background(MaterialTheme.colorScheme.compositeSurfaceElevation(4.dp)) - ) { - Icon( - imageVector = Icons.Rounded.Shuffle, - tint = MaterialTheme.colorScheme.primary, - contentDescription = null, - modifier = Modifier.padding(4.dp).align(Alignment.Center) - ) + if ((item.children[0].events?.click as? HubEvent.PlayFromContext)?.data?.player?.options?.player_options_override?.shuffling_context != false) { + Box( + Modifier + .align(Alignment.BottomEnd) + .offset(4.dp, 4.dp) + .clip(CircleShape) + .size(22.dp) + .background(MaterialTheme.colorScheme.compositeSurfaceElevation(4.dp)) + ) { + Icon( + imageVector = Icons.Rounded.Shuffle, + tint = MaterialTheme.colorScheme.primary, + contentDescription = null, + modifier = Modifier + .padding(4.dp) + .align(Alignment.Center) + ) + } + } } - } } - } } \ 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 ef4e4685..f13ee4fd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -166,4 +166,7 @@ Nothing was found Correct your request and try again playing from search + songs + songs by + "likes • " \ No newline at end of file From a0b209213613137d6a1562144e66b6ec7a935798 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Sun, 4 Dec 2022 12:37:30 +0100 Subject: [PATCH 11/39] Added no lyrics found in the lyrics composition and fullscreen --- .../core/lyrics/SpLyricsController.kt | 13 ++++-- .../ui/screens/hub/PodcastShowScreen.kt | 2 + .../fullscreen/NowPlayingLyricsComposition.kt | 43 ++++++++++++++++--- app/src/main/res/values/strings.xml | 1 + 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/lyrics/SpLyricsController.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/lyrics/SpLyricsController.kt index 2adfa118..b0c749ef 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/lyrics/SpLyricsController.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/lyrics/SpLyricsController.kt @@ -4,6 +4,8 @@ import android.util.Log import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import bruhcollective.itaysonlab.jetispot.R +import bruhcollective.itaysonlab.jetispot.SpApp import bruhcollective.itaysonlab.jetispot.core.SpPlayerServiceManager import com.spotify.lyrics.v2.lyrics.proto.LyricsResponse.LyricsLine import kotlinx.coroutines.CoroutineScope @@ -53,13 +55,18 @@ class SpLyricsController @Inject constructor( } } + @Suppress("SENSELESS_COMPARISON", "LiftReturnOrAssignment") fun setProgress(pos: Long){ val lyricIndex = currentLyricsLines.indexOfLast { it.startTimeMs < pos } - if (lyricIndex == -1) { - currentSongLine = "\uD83C\uDFB5" + if(currentLyricsState == LyricsState.Unavailable){ + currentSongLine = SpApp.context.getString(R.string.no_lyrics) } else { - currentSongLine = currentLyricsLines[lyricIndex].words + if (lyricIndex == -1) { + currentSongLine = "\uD83C\uDFB5" + } else { + currentSongLine = currentLyricsLines[lyricIndex].words + } } } diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/PodcastShowScreen.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/PodcastShowScreen.kt index b3bb048c..193de60e 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/PodcastShowScreen.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/PodcastShowScreen.kt @@ -1,5 +1,6 @@ package bruhcollective.itaysonlab.jetispot.ui.screens.hub +import android.util.Log import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf @@ -19,6 +20,7 @@ fun PodcastShowScreen( viewModel: PodcastShowViewModel = hiltViewModel() ) { LaunchedEffect(Unit) { + Log.d("PodcastShowScreen", "LaunchedEffect - Podcast Id: $id") viewModel.load { viewModel.loadInternal(id) } } diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingLyricsComposition.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingLyricsComposition.kt index e2f7f338..36be8c8c 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingLyricsComposition.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingLyricsComposition.kt @@ -27,6 +27,9 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import bruhcollective.itaysonlab.jetispot.SpApp +import bruhcollective.itaysonlab.jetispot.R +import bruhcollective.itaysonlab.jetispot.core.lyrics.SpLyricsController import bruhcollective.itaysonlab.jetispot.ui.screens.nowplaying.NowPlayingViewModel @Composable @@ -43,8 +46,16 @@ fun NowPlayingLyricsComposition( ) val size = Size( - width = lerp(viewModel.lyricsCardParams.second.width.toFloat(), size.width, rvStateProgress), - height = lerp(viewModel.lyricsCardParams.second.height.toFloat(), size.height, rvStateProgress), + width = lerp( + viewModel.lyricsCardParams.second.width.toFloat(), + size.width, + rvStateProgress + ), + height = lerp( + viewModel.lyricsCardParams.second.height.toFloat(), + size.height, + rvStateProgress + ), ) val radius = androidx.compose.ui.unit.lerp(12.dp, 0.dp, rvStateProgress).toPx() @@ -69,9 +80,31 @@ fun NowPlayingLyricsComposition( }) { LazyColumn(modifier = Modifier.padding(12.dp)) { - items(viewModel.spLyricsController.currentLyricsLines) { line -> - Text(text = line.words, color = Color.White, fontSize = 18.sp, fontWeight = FontWeight.SemiBold) - Spacer(modifier = Modifier.height(4.dp)) + if (viewModel.spLyricsController.currentLyricsState == SpLyricsController.LyricsState.Unavailable) { + items(listOf(SpApp.context.getString(R.string.no_lyrics))) { item -> + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = item, + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) + } + + } + } else { + items(viewModel.spLyricsController.currentLyricsLines) { line -> + Text( + text = line.words, + color = Color.White, + fontSize = 18.sp, + fontWeight = FontWeight.SemiBold + ) + Spacer(modifier = Modifier.height(4.dp)) + } } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f13ee4fd..132a357e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -169,4 +169,5 @@ songs songs by "likes • " + Ups, seems like this song have no lyrics… \ No newline at end of file From 9c64a5ff30a78093bf35a01e32796fb470dc81f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Sun, 4 Dec 2022 13:25:47 +0100 Subject: [PATCH 12/39] Added a little elevation in the fullscreen artwork --- .../fullscreen/NowPlayingControls.kt | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingControls.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingControls.kt index 6cffa79d..fed565b2 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingControls.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingControls.kt @@ -62,20 +62,21 @@ fun NowPlayingControls( private fun ControlsArtwork( viewModel: NowPlayingViewModel, ) { - PreviewableAsyncImage( - imageUrl = remember(viewModel.currentTrack.value) { - if (viewModel.currentQueue.value.isNotEmpty()) { - SpUtils.getImageUrl(viewModel.currentQueue.value[viewModel.currentQueuePosition.value].album.coverGroup.imageList.find { it.size == Metadata.Image.Size.LARGE }?.fileId) - } else { - null - } - }, - placeholderType = "track", - modifier = Modifier - .padding(horizontal = 14.dp) - .size(128.dp) - .clip(RoundedCornerShape(12.dp)) - ) + ElevatedCard(modifier = Modifier.padding(horizontal = 14.dp).clip(RoundedCornerShape(12.dp))) { + PreviewableAsyncImage( + imageUrl = remember(viewModel.currentTrack.value) { + if (viewModel.currentQueue.value.isNotEmpty()) { + SpUtils.getImageUrl(viewModel.currentQueue.value[viewModel.currentQueuePosition.value].album.coverGroup.imageList.find { it.size == Metadata.Image.Size.LARGE }?.fileId) + } else { + null + } + }, + placeholderType = "track", + modifier = Modifier + .size(128.dp) + + ) + } } @OptIn(ExperimentalMaterialApi::class) From 914d47c41bb4d45ee83bd617fd6eab928195dc1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Sun, 4 Dec 2022 14:55:41 +0100 Subject: [PATCH 13/39] Added light theme background in Full screen player --- .../itaysonlab/jetispot/ui/screens/Screen.kt | 1 + .../ui/screens/config/ConfigScreen.kt | 3 +++ .../fullscreen/NowPlayingBackground.kt | 5 +++-- .../itaysonlab/jetispot/ui/theme/Theme.kt | 19 ++++++++++++++++++- app/src/main/res/values/strings.xml | 1 + 5 files changed, 26 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/Screen.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/Screen.kt index 4766b916..1f3c8369 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/Screen.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/Screen.kt @@ -28,6 +28,7 @@ enum class Screen( // config Config("config"), StorageConfig("config/storage"), + LanguageConfig("config/language"), QualityConfig("config/playbackQuality"), NormalizationConfig("config/playbackNormalization"); 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 883a6558..681a4d30 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 @@ -83,6 +83,9 @@ class ConfigScreenViewModel @Inject constructor( add(ConfigItem.Preference(R.string.storage, { ctx, cfg -> "" }, { it.navigate(Screen.StorageConfig) })) + add(ConfigItem.Preference(R.string.language, { ctx, cfg -> "" }, { + it.navigate(Screen.LanguageConfig) + })) add(ConfigItem.Category(R.string.config_account)) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingBackground.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingBackground.kt index 3e170a52..30b0d1c0 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingBackground.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingBackground.kt @@ -4,6 +4,7 @@ import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.Canvas import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding @@ -38,11 +39,11 @@ fun NowPlayingBackground( ) { val currentColor = viewModel.currentBgColor.value val dominantColorAsBg = animateColorAsState(if (currentColor == Color.Transparent) MaterialTheme.colorScheme.surface else currentColor) - + val isSystemInDarkTheme = isSystemInDarkTheme() Canvas(modifier) { drawRect( brush = Brush.radialGradient( - colors = listOf(dominantColorAsBg.value, Color.Black), + colors = listOf(dominantColorAsBg.value, if(isSystemInDarkTheme) Color.Black else Color.White), center = Offset( x = size.width * 0.1f, y = size.height * 0.75f diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/theme/Theme.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/theme/Theme.kt index aebb29cf..592125f3 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/theme/Theme.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/theme/Theme.kt @@ -1,6 +1,10 @@ package bruhcollective.itaysonlab.jetispot.ui.theme +import android.app.Activity +import android.content.Context +import android.content.ContextWrapper import android.os.Build +import android.view.Window import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.* @@ -8,15 +12,28 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.unit.dp +import androidx.core.view.WindowCompat import com.google.accompanist.systemuicontroller.rememberSystemUiController +private tailrec fun Context.findWindow(): Window? = + when (this) { + is Activity -> window + is ContextWrapper -> baseContext.findWindow() + else -> null + } + @Composable fun ApplicationTheme( darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit ) { - val sysUiController = rememberSystemUiController() + val window = LocalView.current.context.findWindow() + val view = LocalView.current + val sysUiController = rememberSystemUiController(window) + + window?.let { WindowCompat.getInsetsController(it, view).isAppearanceLightStatusBars = darkTheme } SideEffect { sysUiController.setSystemBarsColor(color = Color.Transparent, darkIcons = !darkTheme) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 132a357e..0c1471e3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -170,4 +170,5 @@ songs by "likes • " Ups, seems like this song have no lyrics… + Language \ No newline at end of file From d068013bd882eaaaecd974ee019a5803e4342500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Sun, 4 Dec 2022 17:55:24 +0100 Subject: [PATCH 14/39] Default audio quality as High instead of Very High in non-premium users. --- .../jetispot/core/SpConfigurationManager.kt | 7 +- .../itaysonlab/jetispot/core/util/SpUtils.kt | 2 + .../FallbackPlanComponentBinder.kt | 18 ++-- .../jetispot/ui/screens/auth/AuthScreen.kt | 8 +- .../ui/screens/auth/AuthScreenViewModel.kt | 23 +++++ .../ui/screens/config/QualityConfigScreen.kt | 92 +++++++++++-------- .../screens/nowplaying/NowPlayingViewModel.kt | 2 +- app/src/main/res/values/strings.xml | 1 + 8 files changed, 104 insertions(+), 49 deletions(-) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/SpConfigurationManager.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/SpConfigurationManager.kt index 31d5ab15..235f5f2d 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/SpConfigurationManager.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/SpConfigurationManager.kt @@ -5,6 +5,7 @@ import androidx.datastore.core.CorruptionException import androidx.datastore.core.DataStore import androidx.datastore.core.DataStoreFactory import androidx.datastore.core.Serializer +import bruhcollective.itaysonlab.jetispot.SpApp import bruhcollective.itaysonlab.jetispot.proto.AppConfig import bruhcollective.itaysonlab.jetispot.proto.AudioNormalization import bruhcollective.itaysonlab.jetispot.proto.AudioQuality @@ -23,9 +24,11 @@ import javax.inject.Singleton @Singleton class SpConfigurationManager @Inject constructor( - @ApplicationContext private val appContext: Context + @ApplicationContext private val appContext: Context, ) { companion object { + //get spSessionManager session + private val spSessionManager = SpSessionManager(SpApp.context) val EMPTY = object: DataStore { override val data: Flow get() = emptyFlow() override suspend fun updateData(transform: suspend (t: AppConfig) -> AppConfig) = TODO("This is an empty DataStore!") @@ -35,7 +38,7 @@ class SpConfigurationManager @Inject constructor( setPlayerConfig(PlayerConfig.newBuilder().apply { autoplay = true normalization = true - preferredQuality = AudioQuality.VERY_HIGH + preferredQuality = AudioQuality.HIGH normalizationLevel = AudioNormalization.BALANCED crossfade = 0 preload = true diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/SpUtils.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/SpUtils.kt index 82763f45..a2dd3b19 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/SpUtils.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/SpUtils.kt @@ -26,4 +26,6 @@ object SpUtils { fun getScannableUrl(uri: String) = "https://scannables.scdn.co/uri/800/$uri" fun getImageUrl(bytes: ByteString?) = if (bytes != null) "https://i.scdn.co/image/${Utils.bytesToHex(bytes).lowercase()}" else null + //check if it's premium account + } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_plans/FallbackPlanComponentBinder.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_plans/FallbackPlanComponentBinder.kt index 2e79f6ae..a285ce03 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_plans/FallbackPlanComponentBinder.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/dac/components_plans/FallbackPlanComponentBinder.kt @@ -6,10 +6,12 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import bruhcollective.itaysonlab.jetispot.ui.ext.compositeSurfaceElevation import bruhcollective.itaysonlab.jetispot.ui.shared.MediumText import com.spotify.planoverview.v1.FallbackPlanComponent +import bruhcollective.itaysonlab.jetispot.R @Composable fun FallbackPlanComponentBinder( @@ -28,12 +30,16 @@ fun FallbackPlanComponentBinder( .fillMaxWidth() ) { Column(modifier = Modifier.padding(16.dp)) { - MediumText(modifier = Modifier, - text = item.name) - Divider() - MediumText(modifier = Modifier, - text = item.description) - Text("Seems like you don't have an Spotify Account") + MediumText( + modifier = Modifier, + text = item.name + ) + Divider(modifier = Modifier.padding(top = 6.dp, bottom = 6.dp)) + MediumText( + modifier = Modifier, + text = item.description + ) + Text(stringResource(id = R.string.plan_overview_fallback_plan)) } } } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/auth/AuthScreen.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/auth/AuthScreen.kt index 51a328c2..096b0de9 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/auth/AuthScreen.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/auth/AuthScreen.kt @@ -47,6 +47,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import bruhcollective.itaysonlab.jetispot.R +import bruhcollective.itaysonlab.jetispot.proto.AudioQuality import bruhcollective.itaysonlab.jetispot.ui.ext.compositeSurfaceElevation import bruhcollective.itaysonlab.jetispot.ui.navigation.LocalNavigationController import bruhcollective.itaysonlab.jetispot.ui.screens.Dialog @@ -68,6 +69,11 @@ fun AuthScreen( } } + fun onLoginSuccess(){ + navController.navigateAndClearStack(Screen.Feed) + viewModel.updateAudioQualityIfPremium(AudioQuality.VERY_HIGH) + } + val autofill = LocalAutofill.current val focusManager = LocalFocusManager.current @@ -173,7 +179,7 @@ fun AuthScreen( viewModel.auth( username = username, password = password, - onSuccess = { navController.navigateAndClearStack(Screen.Feed) }, + onSuccess = { onLoginSuccess() }, onFailure = setSnackbarContent, ) }, diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/auth/AuthScreenViewModel.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/auth/AuthScreenViewModel.kt index 682205e4..0764d1c2 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/auth/AuthScreenViewModel.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/auth/AuthScreenViewModel.kt @@ -8,20 +8,38 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import bruhcollective.itaysonlab.jetispot.R import bruhcollective.itaysonlab.jetispot.core.SpAuthManager +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 bruhcollective.itaysonlab.jetispot.ui.screens.config.QualityConfigScreenViewModel import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import javax.inject.Inject +import bruhcollective.itaysonlab.jetispot.proto.PlayerConfig as PlayerConfig @Stable @HiltViewModel class AuthScreenViewModel @Inject constructor( private val authManager: SpAuthManager, private val resources: Resources, + private val spSessionManager: SpSessionManager, + private val spConfigurationManager: SpConfigurationManager ) : ViewModel() { private val _isAuthInProgress = mutableStateOf(false) val isAuthInProgress: State = _isAuthInProgress + fun updateAudioQualityIfPremium(audioQuality: AudioQuality) { + viewModelScope.launch { + if (spSessionManager.session?.getUserAttribute("player-license") == "premium") { + modifyDatastore { + playerConfig = playerConfig.toBuilder().setPreferredQuality(audioQuality).build() + } + } + } + } + fun auth( username: String, password: String, @@ -53,4 +71,9 @@ class AuthScreenViewModel @Inject constructor( _isAuthInProgress.value = false } } + suspend fun modifyDatastore(runOnBuilder: AppConfig.Builder.() -> Unit) { + spConfigurationManager.dataStore.updateData { + it.toBuilder().apply(runOnBuilder).build() + } + } } 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 41e61613..8f44485c 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,55 +8,69 @@ 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 @Composable fun QualityConfigScreen( - viewModel: QualityConfigScreenViewModel = hiltViewModel() + viewModel: QualityConfigScreenViewModel = hiltViewModel() ) { - BaseConfigScreen(viewModel) + BaseConfigScreen(viewModel) } @HiltViewModel class QualityConfigScreenViewModel @Inject constructor( - private val spSessionManager: SpSessionManager, - private val spConfigurationManager: SpConfigurationManager + private val spSessionManager: SpSessionManager, + private val spConfigurationManager: SpConfigurationManager ) : ViewModel(), ConfigViewModel { - private val configList = buildList { - add(ConfigItem.Radio(R.string.quality_low, R.string.quality_low_desc, { - it.playerConfig.preferredQuality == AudioQuality.LOW - }, { true }, { - playerConfig = playerConfig.toBuilder().setPreferredQuality(AudioQuality.LOW).build() - })) - - add(ConfigItem.Radio(R.string.quality_normal, R.string.quality_normal_desc, { - it.playerConfig.preferredQuality == AudioQuality.NORMAL - }, { true }, { - playerConfig = playerConfig.toBuilder().setPreferredQuality(AudioQuality.NORMAL).build() - })) - - add(ConfigItem.Radio(R.string.quality_high, R.string.quality_high_desc, { - it.playerConfig.preferredQuality == AudioQuality.HIGH - }, { true }, { - playerConfig = playerConfig.toBuilder().setPreferredQuality(AudioQuality.HIGH).build() - })) - - add(ConfigItem.Radio(R.string.quality_very_high, R.string.quality_very_high_desc, { - it.playerConfig.preferredQuality == AudioQuality.VERY_HIGH - }, { true }, { - playerConfig = playerConfig.toBuilder().setPreferredQuality(AudioQuality.VERY_HIGH).build() - })) - - add(ConfigItem.Info(R.string.warn_quality)) - } - - override fun provideTitle() = R.string.config_pbquality - override fun provideDataStore() = spConfigurationManager.dataStore - override fun provideConfigList() = configList - override suspend fun modifyDatastore (runOnBuilder: AppConfig.Builder.() -> Unit) { - spConfigurationManager.dataStore.updateData { - it.toBuilder().apply(runOnBuilder).build() + + suspend fun updateAudioQuality(audioQuality: AudioQuality) { + kotlin.run { + modifyDatastore { + playerConfig = playerConfig.toBuilder().setPreferredQuality(audioQuality).build() + } + } + } + + private val configList = buildList { + add(ConfigItem.Radio(R.string.quality_low, R.string.quality_low_desc, { + it.playerConfig.preferredQuality == AudioQuality.LOW + }, { true }, { + playerConfig = playerConfig.toBuilder().setPreferredQuality(AudioQuality.LOW).build() + })) + + add(ConfigItem.Radio(R.string.quality_normal, R.string.quality_normal_desc, { + it.playerConfig.preferredQuality == AudioQuality.NORMAL + }, { true }, { + playerConfig = playerConfig.toBuilder().setPreferredQuality(AudioQuality.NORMAL).build() + })) + + add(ConfigItem.Radio(R.string.quality_high, R.string.quality_high_desc, { + it.playerConfig.preferredQuality == AudioQuality.HIGH + }, { true }, { + playerConfig = playerConfig.toBuilder().setPreferredQuality(AudioQuality.HIGH).build() + })) + + //if the user doesn't have premium, don't show the option to select very high quality + if (spSessionManager.session?.getUserAttribute("player-license") == "premium") { + add(ConfigItem.Radio(R.string.quality_very_high, R.string.quality_very_high_desc, { + it.playerConfig.preferredQuality == AudioQuality.VERY_HIGH + }, { true }, { + playerConfig = + playerConfig.toBuilder().setPreferredQuality(AudioQuality.VERY_HIGH).build() + })) + } + + add(ConfigItem.Info(R.string.warn_quality)) + } + + override fun provideTitle() = R.string.config_pbquality + override fun provideDataStore() = spConfigurationManager.dataStore + override fun provideConfigList() = configList + override suspend fun modifyDatastore(runOnBuilder: AppConfig.Builder.() -> Unit) { + spConfigurationManager.dataStore.updateData { + it.toBuilder().apply(runOnBuilder).build() + } } - } } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/NowPlayingViewModel.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/NowPlayingViewModel.kt index 0b3aaeb7..114e771d 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/NowPlayingViewModel.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/NowPlayingViewModel.kt @@ -121,7 +121,7 @@ class NowPlayingViewModel @Inject constructor( fun getHeaderText(): String { return when { - currentContextUri.value.contains("collection") -> SpApp.context.getString(R.string.liked_songs) // TODO: to R.string + currentContextUri.value.contains("collection") -> SpApp.context.getString(R.string.liked_songs) else -> currentContext.value } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0c1471e3..3922a9f0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -171,4 +171,5 @@ "likes • " Ups, seems like this song have no lyrics… Language + Seems like you don\'t have an Spotify Account. \ No newline at end of file From 3bae98205664d50445892f6e4deabdfac681d7f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Sun, 4 Dec 2022 19:40:13 +0100 Subject: [PATCH 15/39] Updated R8 rules. Release build still failing --- app/build.gradle.kts | 2 +- app/proguard-rules.pro | 28 ++-------------------------- 2 files changed, 3 insertions(+), 27 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 164a83df..918c91b7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -14,7 +14,7 @@ apply(plugin = "dagger.hilt.android.plugin") val versionMajor = 0 val versionMinor = 1 -val versionPatch = 1 +val versionPatch = 2 val versionBuild = 0 val isStable = true diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index ac9a20ba..a4ddc9ea 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -54,6 +54,7 @@ -keep class bruhcollective.itaysonlab.swedentricks.** {*;} -keep class bruhcollective.itaysonlab.jetispot.proto.** {*;} -keep class com.google.protobuf.Any {*;} +-keep class com.google.protobuf.GeneratedMessageV3 {*;} -keep class * extends com.google.protobuf.AbstractMessage {*;} # librespot @@ -92,30 +93,5 @@ -keep,allowobfuscation,allowshrinking interface retrofit2.Call -keep,allowobfuscation,allowshrinking class retrofit2.Response -keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation --dontwarn javax.sound.sampled.AudioFormat$Encoding --dontwarn javax.sound.sampled.AudioFormat --dontwarn javax.sound.sampled.AudioSystem --dontwarn javax.sound.sampled.Control$Type --dontwarn javax.sound.sampled.Control --dontwarn javax.sound.sampled.DataLine$Info --dontwarn javax.sound.sampled.FloatControl$Type --dontwarn javax.sound.sampled.FloatControl --dontwarn javax.sound.sampled.Line$Info --dontwarn javax.sound.sampled.Line --dontwarn javax.sound.sampled.LineUnavailableException --dontwarn javax.sound.sampled.Mixer$Info --dontwarn javax.sound.sampled.Mixer --dontwarn javax.sound.sampled.SourceDataLine -dontwarn kotlinx.serialization.KSerializer --dontwarn kotlinx.serialization.Serializable --dontwarn org.apache.logging.log4j.Level --dontwarn org.apache.logging.log4j.core.config.Configurator --dontwarn org.bouncycastle.jsse.BCSSLParameters --dontwarn org.bouncycastle.jsse.BCSSLSocket --dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider --dontwarn org.conscrypt.Conscrypt$Version --dontwarn org.conscrypt.Conscrypt --dontwarn org.conscrypt.ConscryptHostnameVerifier --dontwarn org.openjsse.javax.net.ssl.SSLParameters --dontwarn org.openjsse.javax.net.ssl.SSLSocket --dontwarn org.openjsse.net.ssl.OpenJSSE \ No newline at end of file +-dontwarn kotlinx.serialization.Serializable \ No newline at end of file From 0d4540f40a5c03f4d2b72251211c511304c85ef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Sun, 4 Dec 2022 20:30:49 +0100 Subject: [PATCH 16/39] Fixed release build failing. DON'T EVER USE android.util.Log ! --- .../itaysonlab/jetispot/core/api/SpInternalApi.kt | 2 -- .../itaysonlab/jetispot/ui/AppNavigation.kt | 2 -- .../jetispot/ui/screens/hub/HubScreen.kt | 15 +-------------- 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt index 085a9e3c..d7c1ed36 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt @@ -1,6 +1,5 @@ package bruhcollective.itaysonlab.jetispot.core.api -import android.util.Log import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubResponse import bruhcollective.itaysonlab.jetispot.core.objs.playlists.LikedSongsResponse import bruhcollective.itaysonlab.jetispot.core.objs.tags.ContentFilterResponse @@ -101,7 +100,6 @@ interface SpInternalApi { appName = "ANDROID_MUSIC_APP" version = SpUtils.SPOTIFY_APP_VERSION }.build() - Log.d("SpInternalApi", "buildDacRequestForHome: $this") }.build() } } \ No newline at end of file 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 ecde5ddb..e38dc3ce 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/AppNavigation.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/AppNavigation.kt @@ -1,7 +1,6 @@ package bruhcollective.itaysonlab.jetispot.ui import android.content.Intent -import android.util.Log import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size @@ -73,7 +72,6 @@ fun AppNavigation( DacRendererScreen("", true, { getDacHome(SpInternalApi.buildDacRequestForHome(it)) }) - Log.i("AppNavigation", "Feed loaded") } composable(Screen.SpotifyIdRedirect.route, deepLinks = listOf(navDeepLink { 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 14e67d20..0c164ae5 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 @@ -27,7 +27,6 @@ import bruhcollective.itaysonlab.jetispot.ui.screens.Dialog import bruhcollective.itaysonlab.jetispot.ui.shared.PagingErrorPage import bruhcollective.itaysonlab.jetispot.ui.shared.PagingLoadingPage import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import javax.inject.Inject @@ -153,16 +152,4 @@ class HubScreenViewModel @Inject constructor( class Error(val error: Exception) : State() object Loading : State() } -} - -/*launch(Dispatchers.IO) { - kotlin.runCatching { - val temp = UpdateUtil.checkForUpdate() - if (temp != null) { - latestRelease = temp - showUpdateDialog = true - } - }.onFailure { - it.printStackTrace() - } -}*/ \ No newline at end of file +} \ No newline at end of file From af085323d1677aaadb17f06f8a961748cb872d12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Mon, 5 Dec 2022 00:26:53 +0100 Subject: [PATCH 17/39] Updated listening history to don't have unsupported ids and temporary deleted artists subtitle because of crashing --- .../jetispot/core/api/SpInternalApi.kt | 5 +- .../itaysonlab/jetispot/core/di/ApiModule.kt | 3 + .../jetispot/core/objs/hub/HubComponent.kt | 3 + .../itaysonlab/jetispot/ui/hub/HubBinder.kt | 1 + .../ui/hub/components/ArtistPinnedItem.kt | 2 - .../hub/components/PlaylistTrackRowLarger.kt | 67 ++++++++ .../ui/screens/config/QualityConfigScreen.kt | 2 +- .../screens/history/ListeningHistoryScreen.kt | 5 +- .../jetispot/ui/screens/hub/HubExt.kt | 156 +++++++++++------- 9 files changed, 174 insertions(+), 70 deletions(-) create mode 100644 app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/PlaylistTrackRowLarger.kt diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt index d7c1ed36..9e2ed47c 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt @@ -18,7 +18,7 @@ import java.util.* // TODO: Leave as it right now, later separate into other interfaces interface SpInternalApi { @GET("/homeview/v1/home") - suspend fun getHomeView(@Query("is_car_connected") carConnected: Boolean, @Query("locale") locale: String? = ""): HubResponse + suspend fun getHomeView(@Query("is_car_connected") carConnected: Boolean, @Query("locale") locale: String = "ES"): HubResponse @GET("/chartview/v5/overview/android") suspend fun getChartView(): HubResponse @@ -49,7 +49,7 @@ interface SpInternalApi { suspend fun getCollectionTags(@Query("subjective") subjective: Boolean = true): ContentFilterResponse @POST("/home-dac-viewservice/v1/view") - suspend fun getDacHome(@Body request: DacRequest = buildDacRequestForHome()): DacResponse + suspend fun getDacHome(@Body request: DacRequest = buildDacRequestForHome(), @Query("locale") locale: String = "ES"): DacResponse @GET("/pam-view-service/v1/AllPlans") suspend fun getAllPlans(): DacResponse @@ -95,6 +95,7 @@ interface SpInternalApi { facet = bFacet clientTimezone = TimeZone.getDefault().id putFeatureFlags("ic_flag_enabled", "true") + putFeatureFlags("locale", "ES") }.build()) clientInfo = DacRequest.ClientInfo.newBuilder().apply { appName = "ANDROID_MUSIC_APP" 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 48e29f0b..6ffdbc6e 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 @@ -3,6 +3,7 @@ package bruhcollective.itaysonlab.jetispot.core.di import bruhcollective.itaysonlab.jetispot.core.SpSessionManager import bruhcollective.itaysonlab.jetispot.core.api.* import bruhcollective.itaysonlab.jetispot.core.di.ext.interceptRequest +import bruhcollective.itaysonlab.jetispot.core.util.Log import bruhcollective.itaysonlab.jetispot.core.util.SpUtils import bruhcollective.itaysonlab.jetispot.core.util.create import com.squareup.moshi.Moshi @@ -30,7 +31,9 @@ object ApiModule { interceptRequest { orig -> // 1. Authorization (& client token) header("Authorization", "Bearer ${sessionManager.session.tokens().get("playlist-read")}") + Log.d("Authorization", "Bearer ${sessionManager.session.tokens().get("playlist-read")}") header("client-token", tokenHandler.requestToken()) + Log.d("client-token", tokenHandler.requestToken()) // 2. Default headers header("User-Agent", "Spotify/${SpUtils.SPOTIFY_APP_VERSION} Android/32 (Pixel 4a (5G))") diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/objs/hub/HubComponent.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/objs/hub/HubComponent.kt index 5181fab1..21391280 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/objs/hub/HubComponent.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/objs/hub/HubComponent.kt @@ -26,6 +26,9 @@ sealed class HubComponent { @TypeLabel("artist:likedSongsRow") object ArtistLikedSongs: HubComponent() + @TypeLabel("listeninghistory:playlistContextRow") + object HistoryPlaylist: HubComponent() + // BROWSE @TypeLabel("find:categoryCard") 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 aa998b43..fb41347c 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 @@ -60,6 +60,7 @@ fun HubBinder ( HubComponent.PodcastTopics -> PodcastTopicsStrip(item) HubComponent.OutlinedButton -> OutlineButton(item) + HubComponent.HistoryPlaylist -> PlaylistTrackRowLarger(item) HubComponent.EmptySpace, HubComponent.Ignored -> {} else -> { diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ArtistPinnedItem.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ArtistPinnedItem.kt index 1a79f487..ae26df91 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ArtistPinnedItem.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/ArtistPinnedItem.kt @@ -13,7 +13,6 @@ import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubItem import bruhcollective.itaysonlab.jetispot.ui.hub.clickableHub import bruhcollective.itaysonlab.jetispot.ui.shared.MediumText import bruhcollective.itaysonlab.jetispot.ui.shared.PreviewableAsyncImage -import bruhcollective.itaysonlab.jetispot.ui.shared.Subtext @Composable fun ArtistPinnedItem( @@ -38,7 +37,6 @@ fun ArtistPinnedItem( fontWeight = FontWeight.Normal, modifier = Modifier.padding(bottom = 4.dp) ) - Subtext(item.text.subtitle!!) } } } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/PlaylistTrackRowLarger.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/PlaylistTrackRowLarger.kt new file mode 100644 index 00000000..0cc7ad0b --- /dev/null +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/PlaylistTrackRowLarger.kt @@ -0,0 +1,67 @@ +package bruhcollective.itaysonlab.jetispot.ui.hub.components + +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubItem +import bruhcollective.itaysonlab.jetispot.ui.hub.clickableHub +import bruhcollective.itaysonlab.jetispot.ui.shared.MediumText +import bruhcollective.itaysonlab.jetispot.ui.shared.PreviewableAsyncImage +import bruhcollective.itaysonlab.jetispot.ui.shared.Subtext + +@Composable +fun PlaylistTrackRowLarger( + item: HubItem +) { + val artists = remember(item) { + if (!item.text?.subtitle.isNullOrEmpty()) { + item.text!!.subtitle!! + } else if (item.custom?.get("artists") != null) { + (item.custom["artists"] as List>).joinToString { it["name"].toString() } + } else { + "" + } + } + + Row( + Modifier + .clickableHub(item) + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 8.dp) + ) { + PreviewableAsyncImage( + imageUrl = item.images?.main?.uri, + placeholderType = "track", + modifier = Modifier + .align( + Alignment.CenterVertically + ) + .size(72.dp) + ) + + Column( + Modifier + .padding( + start = 16.dp + ) + .align(Alignment.CenterVertically) + ) { + var drawnTitle = false + + if (!item.text?.title.isNullOrEmpty()) { + drawnTitle = true + MediumText(item.text!!.title!!, fontWeight = FontWeight.Normal) + } + + Subtext( + artists, + modifier = Modifier.padding(top = if (drawnTitle) 4.dp else 8.dp), + maxLines = 1, + ) + } + } +} \ No newline at end of file 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 8f44485c..de01adc9 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 @@ -53,7 +53,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("player-license") == "premium") { + 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/history/ListeningHistoryScreen.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/history/ListeningHistoryScreen.kt index db3de392..9a1590d6 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/history/ListeningHistoryScreen.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/history/ListeningHistoryScreen.kt @@ -41,12 +41,13 @@ class HistoryViewModel @Inject constructor( private val spInternalApi: SpInternalApi, private val spPlayerServiceManager: SpPlayerServiceManager ) : AbsHubViewModel(), HubScreenDelegate { + suspend fun load() = load { - spInternalApi.getListeningHistory() + spInternalApi.getListeningHistory() } suspend fun reload() = reload { - spInternalApi.getListeningHistory() + spInternalApi.getListeningHistory() } override fun play(data: PlayFromContextData) { 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 9d42224b..773c1f1e 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 @@ -29,80 +29,110 @@ import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @Composable fun HubScaffold( - appBarTitle: String, - state: HubState, - viewModel: HubScreenDelegate, - toolbarOptions: ToolbarOptions = ToolbarOptions(), - reloadFunc: suspend () -> Unit + appBarTitle: String, + state: HubState, + viewModel: HubScreenDelegate, + toolbarOptions: ToolbarOptions = ToolbarOptions(), + reloadFunc: suspend () -> Unit ) { - val navController = LocalNavigationController.current - val scope = rememberCoroutineScope() - val scrollBehavior = if (toolbarOptions.alwaysVisible) TopAppBarDefaults.exitUntilCollapsedScrollBehavior() else TopAppBarDefaults.pinnedScrollBehavior() + val navController = LocalNavigationController.current + val scope = rememberCoroutineScope() + val scrollBehavior = + if (toolbarOptions.alwaysVisible) TopAppBarDefaults.exitUntilCollapsedScrollBehavior() else TopAppBarDefaults.pinnedScrollBehavior() - when (state) { - is HubState.Loaded -> { - Scaffold(topBar = { - if (toolbarOptions.big) { - LargeTopAppBar(title = { - Text(appBarTitle, maxLines = 1, overflow = TextOverflow.Ellipsis) - }, navigationIcon = { - IconButton(onClick = { navController.popBackStack() }) { - Icon(Icons.Rounded.ArrowBack, null) - } - }, colors = TopAppBarDefaults.largeTopAppBarColors(), scrollBehavior = scrollBehavior) - } else { - TopAppBar(title = { - Text(appBarTitle, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.alpha(scrollBehavior.state.overlappedFraction)) - }, navigationIcon = { - IconButton(onClick = { navController.popBackStack() }) { - Icon(Icons.Rounded.ArrowBack, null) - } - }, colors = if (toolbarOptions.alwaysVisible) TopAppBarDefaults.smallTopAppBarColors() else TopAppBarDefaults.smallTopAppBarColors( - containerColor = Color.Transparent, - scrolledContainerColor = MaterialTheme.colorScheme.compositeSurfaceElevation(3.dp) - ), scrollBehavior = scrollBehavior) - } - }, modifier = Modifier - .fillMaxSize() - .nestedScroll(scrollBehavior.nestedScrollConnection), contentWindowInsets = WindowInsets(top = 0.dp)) { padding -> - CompositionLocalProvider(LocalHubScreenDelegate provides viewModel) { - LazyColumn( - modifier = Modifier - .fillMaxHeight() - .let { if (toolbarOptions.alwaysVisible) it.padding(padding) else it } - ) { - state.data.apply { - if (header != null) { - item( - key = header.id, - contentType = header.component.javaClass.simpleName, - ) { - HubBinder(header) - } - } + when (state) { + is HubState.Loaded -> { + Scaffold( + topBar = { + if (toolbarOptions.big) { + LargeTopAppBar( + title = { + Text(appBarTitle, maxLines = 1, overflow = TextOverflow.Ellipsis) + }, + navigationIcon = { + IconButton(onClick = { navController.popBackStack() }) { + Icon(Icons.Rounded.ArrowBack, null) + } + }, + colors = TopAppBarDefaults.largeTopAppBarColors(), + scrollBehavior = scrollBehavior + ) + } else { + TopAppBar( + title = { + Text( + appBarTitle, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.alpha(scrollBehavior.state.overlappedFraction) + ) + }, + navigationIcon = { + IconButton(onClick = { navController.popBackStack() }) { + Icon(Icons.Rounded.ArrowBack, null) + } + }, + colors = if (toolbarOptions.alwaysVisible) TopAppBarDefaults.smallTopAppBarColors() else TopAppBarDefaults.smallTopAppBarColors( + containerColor = Color.Transparent, + scrolledContainerColor = MaterialTheme.colorScheme.compositeSurfaceElevation( + 3.dp + ) + ), + scrollBehavior = scrollBehavior + ) + } + }, + modifier = Modifier + .fillMaxSize() + .nestedScroll(scrollBehavior.nestedScrollConnection), + contentWindowInsets = WindowInsets(top = 0.dp) + ) { padding -> + CompositionLocalProvider(LocalHubScreenDelegate provides viewModel) { + LazyColumn( + modifier = Modifier + .fillMaxHeight() + .let { if (toolbarOptions.alwaysVisible) it.padding(padding) else it } + ) { + state.data.apply { + + if (header != null) { + item( + key = header.id, + contentType = header.component.javaClass.simpleName, + ) { + HubBinder(header) + } + } + items( + body, + key = { it.id }, + contentType = { it.component.javaClass.simpleName }) { + Box(modifier = Modifier.animateItemPlacement()) { + HubBinder(it) + } + } - items(body, key = { it.id }, contentType = { it.component.javaClass.simpleName }) { - Box(modifier = Modifier.animateItemPlacement()) { - HubBinder(it) + } + } } - } } - } } - } + is HubState.Error -> PagingErrorPage( + exception = state.error, + onReload = { scope.launch { reloadFunc() } }, + modifier = Modifier.fillMaxSize() + ) + HubState.Loading -> PagingLoadingPage(Modifier.fillMaxSize()) } - is HubState.Error -> PagingErrorPage(exception = state.error, onReload = { scope.launch { reloadFunc() } }, modifier = Modifier.fillMaxSize()) - HubState.Loading -> PagingLoadingPage(Modifier.fillMaxSize()) - } } sealed class HubState { - object Loading: HubState() - class Error (val error: Exception): HubState() - class Loaded (val data: HubResponse): HubState() + object Loading : HubState() + class Error(val error: Exception) : HubState() + class Loaded(val data: HubResponse) : HubState() } class ToolbarOptions( - val big: Boolean = false, - val alwaysVisible: Boolean = false + val big: Boolean = false, + val alwaysVisible: Boolean = false ) \ No newline at end of file From f074a169ad8636fa370f2c8491b98f4b4c5afd58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Mon, 5 Dec 2022 10:11:04 +0100 Subject: [PATCH 18/39] Added an ignore battery optimization hint in settings to keep the app in background --- .../itaysonlab/jetispot/ui/hub/HubBinder.kt | 2 + .../ui/screens/config/ConfigScreen.kt | 3 + .../ui/screens/config/SharedConfigUi.kt | 102 ++++++++++++++++++ app/src/main/res/values-es/strings.xml | 9 ++ app/src/main/res/values/strings.xml | 4 +- 5 files changed, 119 insertions(+), 1 deletion(-) 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 fb41347c..5f417cb5 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 @@ -61,6 +61,8 @@ fun HubBinder ( HubComponent.OutlinedButton -> OutlineButton(item) HubComponent.HistoryPlaylist -> PlaylistTrackRowLarger(item) + + //TODO: Keep adding components searching them in the API (Thunder client) HubComponent.EmptySpace, HubComponent.Ignored -> {} else -> { 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 681a4d30..541d991a 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 @@ -31,6 +31,9 @@ class ConfigScreenViewModel @Inject constructor( private val spConfigurationManager: SpConfigurationManager ) : ViewModel(), ConfigViewModel { private val configList = buildList { + + add(ConfigItem.Hint()) + add(ConfigItem.Category(R.string.config_playback)) add(ConfigItem.Preference(R.string.config_pbquality, { ctx, cfg -> 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 2dcc1545..20a4a2d3 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,29 +1,47 @@ package bruhcollective.itaysonlab.jetispot.ui.screens.config import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.PowerManager +import android.provider.Settings +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.StringRes +import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateColorAsState +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.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.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.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.compositeOver +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.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.datastore.core.DataStore +import bruhcollective.itaysonlab.jetispot.R +import bruhcollective.itaysonlab.jetispot.SpApp.Companion.context import bruhcollective.itaysonlab.jetispot.core.SpConfigurationManager import bruhcollective.itaysonlab.jetispot.proto.AppConfig import bruhcollective.itaysonlab.jetispot.ui.ext.rememberEUCScrollBehavior @@ -51,6 +69,14 @@ fun BaseConfigScreen( val dsConfig = dsConfigState.value val navController = LocalNavigationController.current + //Energy things + val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager + var showBatteryHint by remember { mutableStateOf(!pm.isIgnoringBatteryOptimizations(context.packageName)) } + val launcher = + rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { + showBatteryHint = !pm.isIgnoringBatteryOptimizations(context.packageName) + } + Scaffold(topBar = { LargeTopAppBar(title = { Text(stringResource(viewModel.provideTitle())) @@ -68,6 +94,26 @@ fun BaseConfigScreen( .padding(padding)) { items(viewModel.provideConfigList()) { item -> when (item) { + is ConfigItem.Hint -> { + Column(modifier = Modifier.padding()) { + androidx.compose.animation.AnimatedVisibility( + visible = showBatteryHint, + exit = shrinkVertically() + fadeOut() + ) { + PreferencesHint( + title = stringResource(R.string.battery_configuration), + icon = Icons.Rounded.EnergySavingsLeaf, + description = stringResource(R.string.battery_configuration_desc) + ) { + launcher.launch(Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply { + data = Uri.parse("package:${context.packageName}") + }) + showBatteryHint = !pm.isIgnoringBatteryOptimizations(context.packageName) + } + } + } + } + is ConfigItem.Category -> { ConfigCategory(stringResource(item.title)) } @@ -314,6 +360,59 @@ fun ConfigSlider( } } +@Composable +fun PreferencesHint( + title: String = "Title ".repeat(2), + description: String? = "Description text ".repeat(3), + icon: ImageVector? = Icons.Outlined.Translate, + onClick: () -> Unit = {}, +) { + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 12.dp) + .clip(MaterialTheme.shapes.extraLarge) + .background(MaterialTheme.colorScheme.secondaryContainer) + .clickable { onClick() } + .padding(horizontal = 12.dp, vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + icon?.let { + Icon( + imageVector = icon, + contentDescription = null, + modifier = Modifier + .padding(start = 8.dp, end = 16.dp) + .size(24.dp), + tint = MaterialTheme.colorScheme.secondary + ) + } + Column( + modifier = Modifier + .weight(1f) + .padding(start = if (icon == null) 12.dp else 0.dp, end = 12.dp) + ) { + with(MaterialTheme) { + + Text( + text = title, + maxLines = 1, + style = typography.titleLarge.copy(fontSize = 20.sp), + color = colorScheme.onSecondaryContainer + ) + if (description != null) + Text( + text = description, + color = colorScheme.onSecondaryContainer, + maxLines = 2, overflow = TextOverflow.Ellipsis, + style = typography.bodyMedium, + ) + } + } + } +} + // sealed class ConfigItem { @@ -355,4 +454,7 @@ sealed class ConfigItem { val state: (AppConfig) -> Int, val modify: AppConfig.Builder.(Int) -> Unit ) : ConfigItem() + + class Hint( + ) : ConfigItem() } \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index d44d1848..5ad1afa2 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -128,4 +128,13 @@ No se han encontrado resultados Cambia tu criterio de búsqueda y vuelve a intentarlo Lista de reproducción + reproduciendo desde búsqueda + canciones + canciones por + "me gusta • " + Ups, parece que esta canción no tiene letra... + Idioma + Parece que no tienes una cuenta de Spotify Premium. + Configuración de la batería + Ignora la optimización de batería del sistema para esta app para poder mantenerse en segundo plano. \ 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 3922a9f0..47247847 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -171,5 +171,7 @@ "likes • " Ups, seems like this song have no lyrics… Language - Seems like you don\'t have an Spotify Account. + Seems like you don\'t have an Spotify Premium account. + Battery configuration + Ignore system\'s battery optimization to be able to keep the app in the background. \ No newline at end of file From e6dfaa4af6d7f66708441be5b1a12ca74057ed82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Tue, 6 Dec 2022 00:45:37 +0100 Subject: [PATCH 19/39] Updated some strings --- app/src/main/res/values-es/strings.xml | 4 ++-- app/src/main/res/values/strings.xml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 5ad1afa2..ec5b4a1e 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -110,7 +110,7 @@ Biblioteca Tu biblioteca Predeterminado del sistema - Para usar Jetispot necesitas una cuenta premium... O no jeje + Para usar Jetispot necesitas una cuenta premium… Crossfade Deshabilitado Miembro del plan @@ -132,7 +132,7 @@ canciones canciones por "me gusta • " - Ups, parece que esta canción no tiene letra... + Ups, parece que esta canción no tiene letra… Idioma Parece que no tienes una cuenta de Spotify Premium. Configuración de la batería diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 47247847..6f71010c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -174,4 +174,5 @@ Seems like you don\'t have an Spotify Premium account. Battery configuration Ignore system\'s battery optimization to be able to keep the app in the background. + Dismiss \ No newline at end of file From 27076a854829c12bf722436338484637bf2de7a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Tue, 6 Dec 2022 00:55:28 +0100 Subject: [PATCH 20/39] Updated xy position of the dominant color in the fullscreen player --- .../screens/nowplaying/fullscreen/NowPlayingBackground.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingBackground.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingBackground.kt index 30b0d1c0..b0c1be7e 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingBackground.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingBackground.kt @@ -45,10 +45,10 @@ fun NowPlayingBackground( brush = Brush.radialGradient( colors = listOf(dominantColorAsBg.value, if(isSystemInDarkTheme) Color.Black else Color.White), center = Offset( - x = size.width * 0.1f, - y = size.height * 0.75f + x = size.width * 0.2f, + y = size.height * 0.55f ), - radius = size.width * 1.5f + radius = size.width * 1.3f ) ) } From fff1288033f4fadf4ac8a323cc9942a5f8d9b718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Tue, 6 Dec 2022 16:53:05 +0100 Subject: [PATCH 21/39] Made some UI changes --- app/build.gradle.kts | 2 +- .../jetispot/core/api/SpInternalApi.kt | 3 +- .../jetispot/ui/hub/HubEventHandler.kt | 3 + .../ui/hub/components/CollectionHeader.kt | 2 +- .../ui/hub/components/EpisodeListItem.kt | 58 +++++++++---------- .../ui/screens/hub/PodcastShowScreen.kt | 45 +++++++------- 6 files changed, 59 insertions(+), 54 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 918c91b7..a4458b8a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -14,7 +14,7 @@ apply(plugin = "dagger.hilt.android.plugin") val versionMajor = 0 val versionMinor = 1 -val versionPatch = 2 +val versionPatch = 3 val versionBuild = 0 val isStable = true diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt index 9e2ed47c..8f65ee49 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt @@ -18,7 +18,7 @@ import java.util.* // TODO: Leave as it right now, later separate into other interfaces interface SpInternalApi { @GET("/homeview/v1/home") - suspend fun getHomeView(@Query("is_car_connected") carConnected: Boolean, @Query("locale") locale: String = "ES"): HubResponse + suspend fun getHomeView(@Query("is_car_connected") carConnected: Boolean): HubResponse @GET("/chartview/v5/overview/android") suspend fun getChartView(): HubResponse @@ -95,7 +95,6 @@ interface SpInternalApi { facet = bFacet clientTimezone = TimeZone.getDefault().id putFeatureFlags("ic_flag_enabled", "true") - putFeatureFlags("locale", "ES") }.build()) clientInfo = DacRequest.ClientInfo.newBuilder().apply { appName = "ANDROID_MUSIC_APP" diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/HubEventHandler.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/HubEventHandler.kt index 645ae505..55be9a4b 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/HubEventHandler.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/HubEventHandler.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Stable import androidx.compose.ui.Modifier import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubEvent import bruhcollective.itaysonlab.jetispot.core.objs.hub.HubItem +import bruhcollective.itaysonlab.jetispot.core.util.Log import bruhcollective.itaysonlab.jetispot.ui.navigation.NavigationController import bruhcollective.itaysonlab.jetispot.ui.shared.navAndHubClickable import bruhcollective.itaysonlab.jetispot.ui.shared.navClickable @@ -18,7 +19,9 @@ object HubEventHandler { navController.navigate(event.data.uri) } } + is HubEvent.PlayFromContext -> delegate.play(event.data) + HubEvent.Unknown -> {} } } diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/CollectionHeader.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/CollectionHeader.kt index 8fcd5285..34f29218 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/CollectionHeader.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/CollectionHeader.kt @@ -54,7 +54,7 @@ fun CollectionHeader( .padding(horizontal = 16.dp) .padding(top = 8.dp)) - Subtext(text = "${item.custom!!["count"]}" + stringResource(id = R.string.songs), fontSize = 14.sp, modifier = Modifier + Subtext(text = "${item.custom!!["count"]} " + stringResource(id = R.string.songs), fontSize = 14.sp, modifier = Modifier .padding(horizontal = 16.dp) .padding(top = 2.dp)) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/EpisodeListItem.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/EpisodeListItem.kt index 6e40218b..3f945f9a 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/EpisodeListItem.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/hub/components/EpisodeListItem.kt @@ -52,21 +52,21 @@ fun EpisodeListItem( } Column( - Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 8.dp) + Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp) ) { Row { PreviewableAsyncImage( imageUrl = imageUrl, placeholderType = "podcast", modifier = Modifier - .size(56.dp) - .clip(RoundedCornerShape(8.dp)) + .size(56.dp) + .clip(RoundedCornerShape(8.dp)) ) Text( text = episode.name, modifier = Modifier - .padding(start = 16.dp) - .align(Alignment.CenterVertically), + .padding(start = 16.dp) + .align(Alignment.CenterVertically), color = MaterialTheme.colorScheme.onSurface, maxLines = 2, overflow = TextOverflow.Ellipsis @@ -92,8 +92,8 @@ fun EpisodeListItem( tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), contentDescription = null, modifier = Modifier - .size(16.dp) - .align(Alignment.CenterVertically) + .size(16.dp) + .align(Alignment.CenterVertically) ) Text( text = " • ", @@ -114,10 +114,10 @@ fun EpisodeListItem( Row { IconButton( onClick = { /*TODO*/ }, - Modifier - .offset(y = 2.dp) - .align(Alignment.CenterVertically) - .size(28.dp) + Modifier + .offset(y = 2.dp) + .align(Alignment.CenterVertically) + .size(28.dp) ) { Icon(Icons.Rounded.AddCircle, null) } @@ -126,10 +126,10 @@ fun EpisodeListItem( IconButton( onClick = { /*TODO*/ }, - Modifier - .offset(y = 2.dp) - .align(Alignment.CenterVertically) - .size(28.dp) + Modifier + .offset(y = 2.dp) + .align(Alignment.CenterVertically) + .size(28.dp) ) { Icon(Icons.Rounded.Share, null) } @@ -137,29 +137,29 @@ fun EpisodeListItem( Spacer(Modifier.weight(1f)) Box( - Modifier - .clickableHub(item) - .size(28.dp) - .clip(CircleShape) - .background(MaterialTheme.colorScheme.primary) + Modifier + .clickableHub(item) + .size(28.dp) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.primary) ) { Icon( imageVector = Icons.Rounded.PlayArrow, tint = MaterialTheme.colorScheme.onPrimary, contentDescription = null, modifier = Modifier - .size(24.dp) - .align(Alignment.Center) + .size(24.dp) + .align(Alignment.Center) ) } } Box( - Modifier - .padding(top = 16.dp) - .background(MaterialTheme.colorScheme.compositeSurfaceElevation(8.dp)) - .fillMaxWidth() - .height(1.dp) + Modifier + .padding(top = 16.dp) + .background(MaterialTheme.colorScheme.compositeSurfaceElevation(8.dp)) + .fillMaxWidth() + .height(1.dp) ) {} } } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/PodcastShowScreen.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/PodcastShowScreen.kt index 193de60e..0d93ddd8 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/PodcastShowScreen.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/hub/PodcastShowScreen.kt @@ -16,34 +16,37 @@ import javax.inject.Inject @Composable fun PodcastShowScreen( - id: String, - viewModel: PodcastShowViewModel = hiltViewModel() + id: String, + viewModel: PodcastShowViewModel = hiltViewModel() ) { - LaunchedEffect(Unit) { - Log.d("PodcastShowScreen", "LaunchedEffect - Podcast Id: $id") - viewModel.load { viewModel.loadInternal(id) } - } + LaunchedEffect(Unit) { + Log.d("PodcastShowScreen", "LaunchedEffect - Podcast Id: $id") + viewModel.load { viewModel.loadInternal(id) } + } - HubScaffold( - appBarTitle = viewModel.title.value, - state = viewModel.state, - viewModel = viewModel - ) { - viewModel.reload { viewModel.loadInternal(id) } - } + HubScaffold( + appBarTitle = viewModel.title.value, + state = viewModel.state, + viewModel = viewModel + ) { + viewModel.reload { viewModel.loadInternal(id) } + } } @HiltViewModel class PodcastShowViewModel @Inject constructor( - private val spSessionManager: SpSessionManager, - private val spPartnersApi: SpPartnersApi, - private val spPlayerServiceManager: SpPlayerServiceManager, - private val spMetadataRequester: SpMetadataRequester + private val spSessionManager: SpSessionManager, + private val spPartnersApi: SpPartnersApi, + private val spPlayerServiceManager: SpPlayerServiceManager, + private val spMetadataRequester: SpMetadataRequester ) : AbsHubViewModel() { - val title = mutableStateOf("") + val title = mutableStateOf("") - suspend fun loadInternal(id: String) = ShowEntityView.create(spSessionManager, spMetadataRequester, id).also { title.value = it.title ?: "" } + suspend fun loadInternal(id: String) = + ShowEntityView.create(spSessionManager, spMetadataRequester, id) + .also { title.value = it.title ?: "" } - override fun play(data: PlayFromContextData) = play(spPlayerServiceManager, data) - override suspend fun calculateDominantColor(url: String, dark: Boolean) = calculateDominantColor(spPartnersApi, url, dark) + override fun play(data: PlayFromContextData) = play(spPlayerServiceManager, data) + override suspend fun calculateDominantColor(url: String, dark: Boolean) = + calculateDominantColor(spPartnersApi, url, dark) } \ No newline at end of file From 6cb4045e589aa6363c6ad6996b3c969e0374828f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= <60316747+BobbyESP@users.noreply.github.com> Date: Tue, 6 Dec 2022 16:58:16 +0100 Subject: [PATCH 22/39] Update README.md --- README.md | 62 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 702102e7..9d726671 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,59 @@ -## Jetispot -_probably usable __UNOFFICIAL__ Spotify client for Android, built with Jetpack Compose and librespot-java_ +
+
+

Jetispot

-#### Spotify Premium account is REQUIRED*. Offline caching, DRM bypassing or raw file downloading is prohibted by ToS and will NEVER be implemented in Jetispot. Don't waste your time trying to request these features. +
+ +A Spotify unofficial client built with Jetpack Compose, Material You/3 and librespot-java -__What's working:__ -- sign in (login/pass only, no FB/Meta/whatsoever support, no Smart Lock either) -- "browse", "home", album, premium plan, artist and genre screens (some of the blocks might be unsupported) -- library: "liked songs" w/ tag&sort support, rootlist (liked playlists) + pins + artist/album support w/ nice animations, delta updates + pub/sub processing support -- basic playback w/ Spotify Connect support (connect support is very WIP) -- fairly optimized R8 rules, providing __approx. 5-6 megabytes__ release APK size (with the playback and protobuf parts!) +
-__What's in progress:__ +## 📣 NOTICE +Spotify Premium account is **REQUIRED***. Offline caching, DRM bypassing or raw file downloading is prohibted by ToS and will NEVER be implemented in Jetispot. Don't waste your time trying to request these features. + +## 🔮 App features +- Sign In (login/pass only, no FB/Meta/Google support, no Smart Lock either) +- "Browse", "Home", Album, Premium Plans overview, Artists and Genres screens (some of the blocks might be unsupported). +- Library: "liked songs" with tag & sort support, rootlist (liked playlists) + pins + artist/album support with nice animations, delta updates and also pub/sub processing support +- 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 + +
+image +image +image +image +image +image +
+ + +## 🔨 What's in progress - "Now Playing" improvements -- better service (notification improvements) +- Better playback service (notification improvements) +- Fixing "unsupported" warnings -__Application stack:__ -- playback: librespot-java as the core + sinks/decoders from librespot-android + Media2 for the mediasession support -- UI: Jetpack Compose +## 👷 App specifications +- Playback: librespot-java as the core + sinks/decoders from librespot-android + Media2 for the mediasession support +- UI: Jetpack Compose with Material You - DI: Hilt/Dagger -- network: Retrofit w/ Moshi+Protobuf converters +- network: Retrofit w/ Moshi + Protobuf converters - pictures: Coil - storage: Room (collection), MMKV (metadata) - arch: MVVM -- preferences: Jetpack Datastore (proto) +- preferences: Jetpack Datastore (proto) [maybe in a future MMKV will be used for some app variables] + +## ⬇️ Downloads +You can go to the [releases page](https://github.com/BobbyESP/Jetispot/releases) and download any version updated. -__Credits:__ +## Credits - [librespot-java](https://github.com/librespot-org/librespot-java) for the core API part and playback - [librespot-android](https://github.com/devgianlu/librespot-android) for sink and decoder source (in Jetispot they are rewritten to Kotlin) - [moshi](https://github.com/square/moshi/) and [moshix](https://github.com/ZacSweers/MoshiX/) for the undocumented API JSON parsing - [VK Icons](https://github.com/VKCOM/icons) for the amazing icon set used in the application's icon - [MMKV](https://github.com/Tencent/MMKV) for ultra-fast way to cache entity extended metadata -- Google for Android/Jetpack/Hilt +- Google for Jetpack Compose, Protocol Buffers and Material UI components -_* I heard some people can log in with a free account, but I won't provide any assistance to people without premium subscription. There is a possibility that a subscription check may be added to the client side in the future._ +_* Some people can actually login without an Spotify Premium account. Assistance to this accounts may be not be provided and you risk yourself for using a free acount. We will not be held responsible for any ban._ From c4da110e23dd3db8dbd8dfbee042950181101238 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= <60316747+BobbyESP@users.noreply.github.com> Date: Tue, 6 Dec 2022 17:09:54 +0100 Subject: [PATCH 23/39] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9d726671..8fed6628 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ Spotify Premium account is **REQUIRED***. Offline caching, DRM bypassing or raw You can go to the [releases page](https://github.com/BobbyESP/Jetispot/releases) and download any version updated. ## Credits +- [iTaysonLab](https://github.com/iTaysonLab) for creating this project. My fork is a continuation of it's work. If some day is interested in working back to the project, I'll be making a PR for merging changes. - [librespot-java](https://github.com/librespot-org/librespot-java) for the core API part and playback - [librespot-android](https://github.com/devgianlu/librespot-android) for sink and decoder source (in Jetispot they are rewritten to Kotlin) - [moshi](https://github.com/square/moshi/) and [moshix](https://github.com/ZacSweers/MoshiX/) for the undocumented API JSON parsing From a5ac794763dc9cd194f3e09c58500ab11d830ac9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Tue, 6 Dec 2022 23:51:46 +0100 Subject: [PATCH 24/39] OMG, LOCALIZATION FUNCTION WERE JUST HEADERS --- .../jetispot/core/api/SpInternalApi.kt | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt index 8f65ee49..e6d418c3 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt @@ -24,38 +24,38 @@ interface SpInternalApi { suspend fun getChartView(): HubResponse @GET("/radio-apollo/v5/radio-hub") - suspend fun getRadioHub(): HubResponse + suspend fun getRadioHub(@Header("Accept-Language") language:String = Locale.getDefault().language): HubResponse @GET("/me/tracks") suspend fun getSavedTracks(): LikedSongsResponse @GET("/hubview-mobile-v1/browse/{id}") - suspend fun getBrowseView(@Path("id") pageId: String = ""): HubResponse + suspend fun getBrowseView(@Path("id") pageId: String = "", @Header("Accept-Language") language:String = Locale.getDefault().language): HubResponse @GET("/album-entity-view/v2/album/{id}") - suspend fun getAlbumView(@Path("id") pageId: String, @Query("checkDeviceCapability") checkDeviceCapability: Boolean = true): HubResponse + suspend fun getAlbumView(@Path("id") pageId: String, @Query("checkDeviceCapability") checkDeviceCapability: Boolean = true, @Header("Accept-Language") language:String = Locale.getDefault().language): HubResponse @GET("/artistview/v1/artist/{id}") - suspend fun getArtistView(@Path("id") pageId: String, @Query("purchase_allowed") purchaseAllowed: Boolean = false, @Query("timeFormat") timeFormat: String = "24h"): HubResponse + suspend fun getArtistView(@Path("id") pageId: String, @Query("purchase_allowed") purchaseAllowed: Boolean = false, @Query("timeFormat") timeFormat: String = "24h", @Header("Accept-Language") language:String = Locale.getDefault().language): HubResponse @GET("/artistview/v1/artist/{id}/releases") - suspend fun getReleasesView(@Path("id") pageId: String, @Query("checkDeviceCapability") checkDeviceCapability: Boolean = true): HubResponse + suspend fun getReleasesView(@Path("id") pageId: String, @Query("checkDeviceCapability") checkDeviceCapability: Boolean = true, @Header("Accept-Language") language:String = Locale.getDefault().language): HubResponse @GET("/listening-history/v2/mobile/{timestamp}") - suspend fun getListeningHistory(@Path("timestamp") timestamp: String = "", @Query("type") type: String = "merged", @Query("last_component_had_play_context") idk: Boolean = false): HubResponse + suspend fun getListeningHistory(@Path("timestamp") timestamp: String = "", @Query("type") type: String = "merged", @Query("last_component_had_play_context") idk: Boolean = false, @Header("Accept-Language") language:String = Locale.getDefault().language): HubResponse @GET("/content-filter/v1/liked-songs") - @Headers("Accept: application/json", "Accept-Language: en-US") - suspend fun getCollectionTags(@Query("subjective") subjective: Boolean = true): ContentFilterResponse + @Headers("Accept: application/json") + suspend fun getCollectionTags(@Query("subjective") subjective: Boolean = true, @Header("Accept-Language") language:String = Locale.getDefault().language): ContentFilterResponse @POST("/home-dac-viewservice/v1/view") - suspend fun getDacHome(@Body request: DacRequest = buildDacRequestForHome(), @Query("locale") locale: String = "ES"): DacResponse + suspend fun getDacHome(@Body request: DacRequest = buildDacRequestForHome(), @Header("Accept-Language") acceptLanguage: String = Locale.getDefault().language): DacResponse @GET("/pam-view-service/v1/AllPlans") - suspend fun getAllPlans(): DacResponse + suspend fun getAllPlans(@Header("Accept-Language") language:String = Locale.getDefault().language): DacResponse @GET("/pam-view-service/v1/PlanOverview") - suspend fun getPlanOverview(): DacResponse + suspend fun getPlanOverview(@Header("Accept-Language") language:String = Locale.getDefault().language): DacResponse @GET("/popcount/v2/playlist/{id}/count") suspend fun getPlaylistPopCount(@Path("id") id: String = ""): Popcount2External.PopcountResult From 64b8b4102a2231469cb0b745d3ccea0f725c178e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Thu, 8 Dec 2022 01:08:34 +0100 Subject: [PATCH 25/39] Fixed battery optimization hint not working in some devices --- app/src/main/AndroidManifest.xml | 1 + .../itaysonlab/jetispot/core/api/SpInternalApi.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index aa0748de..5e019d42 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,6 +5,7 @@ + diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt index e6d418c3..6ddde1d7 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/api/SpInternalApi.kt @@ -46,7 +46,7 @@ interface SpInternalApi { @GET("/content-filter/v1/liked-songs") @Headers("Accept: application/json") - suspend fun getCollectionTags(@Query("subjective") subjective: Boolean = true, @Header("Accept-Language") language:String = Locale.getDefault().language): ContentFilterResponse + suspend fun getCollectionTags(@Query("subjective") subjective: Boolean = true): ContentFilterResponse @POST("/home-dac-viewservice/v1/view") suspend fun getDacHome(@Body request: DacRequest = buildDacRequestForHome(), @Header("Accept-Language") acceptLanguage: String = Locale.getDefault().language): DacResponse From 3f1137c611cb38f52e657ea2349f7e8d61463705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Thu, 8 Dec 2022 01:09:34 +0100 Subject: [PATCH 26/39] Deleted some log writings for user's security --- .../bruhcollective/itaysonlab/jetispot/core/di/ApiModule.kt | 2 -- 1 file changed, 2 deletions(-) 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 6ffdbc6e..98710dd1 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 @@ -31,9 +31,7 @@ object ApiModule { interceptRequest { orig -> // 1. Authorization (& client token) header("Authorization", "Bearer ${sessionManager.session.tokens().get("playlist-read")}") - Log.d("Authorization", "Bearer ${sessionManager.session.tokens().get("playlist-read")}") header("client-token", tokenHandler.requestToken()) - Log.d("client-token", tokenHandler.requestToken()) // 2. Default headers header("User-Agent", "Spotify/${SpUtils.SPOTIFY_APP_VERSION} Android/32 (Pixel 4a (5G))") From 1d0404bb33b4ff300b30f708c9af8847fc820966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= <60316747+BobbyESP@users.noreply.github.com> Date: Thu, 8 Dec 2022 10:15:45 +0100 Subject: [PATCH 27/39] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 8fed6628..5c3b02be 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,9 @@ A Spotify unofficial client built with Jetpack Compose, Material You/3 and libre +## FORK +I don't want confusions so I'm telling this. This repository is just a fork of the one made by [iTaysonLab](https://github.com/iTaysonLab/jetispot) as I say in the credtis. At the moment of writing this, the project is kind of abandonated. By what I know, the original creator of the app is making an amazing Steam client for Android, Jetisteam. I will be adding new features to the app and also fixing bugs. If in some day Tayson want to go back to the project, I'll be glad to open a Pull Request to have all my work merged in it's original repository. Thanks for your comprehension! + ## 📣 NOTICE Spotify Premium account is **REQUIRED***. Offline caching, DRM bypassing or raw file downloading is prohibted by ToS and will NEVER be implemented in Jetispot. Don't waste your time trying to request these features. From 15c8d0c6a814bdc7b7033e40e7961fe83f944779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= <60316747+BobbyESP@users.noreply.github.com> Date: Thu, 8 Dec 2022 10:16:23 +0100 Subject: [PATCH 28/39] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5c3b02be..05311d7e 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ A Spotify unofficial client built with Jetpack Compose, Material You/3 and libre -## FORK +## ⚠️ FORK I don't want confusions so I'm telling this. This repository is just a fork of the one made by [iTaysonLab](https://github.com/iTaysonLab/jetispot) as I say in the credtis. At the moment of writing this, the project is kind of abandonated. By what I know, the original creator of the app is making an amazing Steam client for Android, Jetisteam. I will be adding new features to the app and also fixing bugs. If in some day Tayson want to go back to the project, I'll be glad to open a Pull Request to have all my work merged in it's original repository. Thanks for your comprehension! ## 📣 NOTICE From 9e39c8b68da01cb5874352cd624e49655390b5f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Thu, 8 Dec 2022 10:23:51 +0100 Subject: [PATCH 29/39] Added issue and feature request templates for GitHub. --- .github/ISSUE_TEMPLATE/bug_report.yml | 54 ++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.yml | 41 ++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000..7b135405 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,54 @@ +name: Bug Report +description: Create a report to help us improve +title: "[Bug Report]" +labels: [bug] +body: + + + + - type: textarea + attributes: + label: Describe the bug + description: + placeholder: | + A clear and concise description of what the bug is. + validations: + required: false + + - type: textarea + attributes: + label: To Reproduce + placeholder: | + Steps to reproduce the behavior: + 1.Go to '...' + 2.Click on '....' + 3.Scroll down to '....' + 4.See error + validations: + required: false + + - type: textarea + attributes: + label: Error reports & Screenshots + placeholder: | + When the error happened, click at the bottom of the page on Copy Details and paste it here. + validations: + required: false + + + - type: textarea + attributes: + label: Device info + description: | + Please provide some information of the device you are using. + placeholder: | + App version, OS version, device model, etc... + validations: + required: false + + - type: textarea + attributes: + label: Additional context + description: + placeholder: | + Add any other context about the problem here. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 00000000..26814da6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,41 @@ +name: Feature Request +description: Suggest a new feature for Jetispot +title: "[Feature Request]" +labels: [enhancement] +body: + - type: checkboxes + id: checklist + attributes: + label: Checklist + description: | + Even if you're not sure about the answer, feel free to leave it blank and provide us with more information about this request. + options: + - label: This feature is merely a UI/UX update. + required: false + - label: This feature is not going to conflict with many of the existing options. + required: false + - type: textarea + id: description_1 + attributes: + label: Is your feature request related to a problem? Please describe. + description: + placeholder: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + validations: + required: false + - type: textarea + id: description_2 + attributes: + label: Describe the solution you'd like + description: + placeholder: A clear and concise description of what you want to happen. + validations: + required: false + - type: textarea + id: description_4 + attributes: + label: Additional context + description: + placeholder: Add any other context or screenshots about the feature request here. + validations: + required: false + render: shell \ No newline at end of file From 28132f28ade190d3dff94b4b0bff400bd8c4ff43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Thu, 8 Dec 2022 14:38:21 +0100 Subject: [PATCH 30/39] Added in-app updating. --- app/build.gradle.kts | 3 +- app/src/main/AndroidManifest.xml | 11 ++ .../jetispot/core/util/UpdateUtil.kt | 9 +- .../itaysonlab/jetispot/ui/AppNavigation.kt | 131 +++++++++++++++++- app/src/main/res/values/strings.xml | 2 + app/src/main/res/xml/provider_paths.xml | 6 + 6 files changed, 151 insertions(+), 11 deletions(-) create mode 100644 app/src/main/res/xml/provider_paths.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a4458b8a..b9ac7a6d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -9,12 +9,13 @@ plugins { id("dagger.hilt.android.plugin") id("kotlin-kapt") id("com.google.protobuf") version "0.8.18" + kotlin("plugin.serialization") version "1.4.21" } apply(plugin = "dagger.hilt.android.plugin") val versionMajor = 0 val versionMinor = 1 -val versionPatch = 3 +val versionPatch = 4 val versionBuild = 0 val isStable = true diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5e019d42..115c17e3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -52,6 +53,16 @@ android:value="true" /> + + + + client.newCall(requestForLatestRelease).enqueue(object : Callback { @@ -102,7 +101,7 @@ object UpdateUtil { startActivity(intent) }.onFailure { throwable: Throwable -> throwable.printStackTrace() - + Log.e(TAG, "installLatestApk: ", throwable) } } @@ -110,7 +109,6 @@ object UpdateUtil { context: Context = SpApp.context, latestRelease: LatestRelease ): Flow = withContext(Dispatchers.IO) { - val apkVersion = context.packageManager.getPackageArchiveInfo( context.getLatestApk().absolutePath, 0 )?.versionName?.toVersion() ?: Version() @@ -136,7 +134,6 @@ object UpdateUtil { val targetUrl = latestRelease.assets?.find { return@find it.name?.contains(preferredArch) ?: false }?.browserDownloadUrl ?: return@withContext emptyFlow() - val request = Request.Builder().url(targetUrl).build() try { val response = client.newCall(request).execute() 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 e38dc3ce..8002a9a6 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/AppNavigation.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/AppNavigation.kt @@ -1,13 +1,26 @@ package bruhcollective.itaysonlab.jetispot.ui +import android.Manifest import android.content.Intent +import android.net.Uri +import android.os.Build +import android.provider.Settings +import android.widget.Toast +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.NewReleases +import androidx.compose.material.icons.rounded.Download import androidx.compose.material.icons.rounded.Warning import androidx.compose.material3.* import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -15,9 +28,11 @@ import androidx.compose.ui.unit.dp import androidx.navigation.* import androidx.navigation.compose.* import bruhcollective.itaysonlab.jetispot.R +import bruhcollective.itaysonlab.jetispot.SpApp.Companion.context 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.UpdateUtil import bruhcollective.itaysonlab.jetispot.ui.bottomsheets.jump_to_artist.JumpToArtistBottomSheet import bruhcollective.itaysonlab.jetispot.ui.screens.BottomSheet import bruhcollective.itaysonlab.jetispot.ui.screens.Dialog @@ -33,6 +48,9 @@ import bruhcollective.itaysonlab.jetispot.ui.screens.search.SearchScreen import bruhcollective.itaysonlab.jetispot.ui.screens.yourlibrary2.YourLibraryContainerScreen import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi import com.google.accompanist.navigation.material.bottomSheet +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch @OptIn(ExperimentalMaterialNavigationApi::class) @Composable @@ -42,12 +60,53 @@ fun AppNavigation( authManager: SpAuthManager, modifier: Modifier ) { + + var showUpdateDialog by rememberSaveable { mutableStateOf(false) } + var currentDownloadStatus by remember { mutableStateOf(UpdateUtil.DownloadStatus.NotYet as UpdateUtil.DownloadStatus) } + val scope = rememberCoroutineScope() + var updateJob: Job? = null + var latestRelease by remember { mutableStateOf(UpdateUtil.LatestRelease()) } + val settings = + rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { + UpdateUtil.installLatestApk() + } + val launcher = rememberLauncherForActivityResult( + ActivityResultContracts.RequestPermission() + ) { result -> + if (result) { + UpdateUtil.installLatestApk() + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (!context.packageManager.canRequestPackageInstalls()) + settings.launch( + Intent( + Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, + Uri.parse("package:${context.packageName}"), + ) + ) + else + UpdateUtil.installLatestApk() + } + } + } + LaunchedEffect(Unit) { if (sessionManager.isSignedIn()) return@LaunchedEffect authManager.authStored() navController.navigate(if (sessionManager.isSignedIn()) Screen.Feed.route else Screen.Authorization.route) { popUpTo(Screen.NavGraph.route) } + launch(Dispatchers.IO) { + kotlin.runCatching { + val temp = UpdateUtil.checkForUpdate() + if (temp != null) { + latestRelease = temp + showUpdateDialog = true + } + }.onFailure { + it.printStackTrace() + } + } } NavHost( @@ -142,13 +201,77 @@ fun AppNavigation( }) } - dialog(Dialog.UpdateAvailable.route){ - //TODO: implement update dialog - } - bottomSheet(BottomSheet.JumpToArtist.route) { entry -> val data = remember { entry.arguments!!.getString("artistIdsAndRoles")!! } JumpToArtistBottomSheet(data = data) } } + + if (showUpdateDialog) { + UpdateDialog( + onDismissRequest = { + showUpdateDialog = false + updateJob?.cancel() + }, + title = latestRelease.name.toString(), + onConfirmUpdate = { + updateJob = scope.launch(Dispatchers.IO) { + kotlin.runCatching { + UpdateUtil.downloadApk(latestRelease = latestRelease) + .collect { downloadStatus -> + currentDownloadStatus = downloadStatus + if (downloadStatus is UpdateUtil.DownloadStatus.Finished) { + launcher.launch(Manifest.permission.REQUEST_INSTALL_PACKAGES) + } + } + }.onFailure { + it.printStackTrace() + currentDownloadStatus = UpdateUtil.DownloadStatus.NotYet + //TODO: Show error + return@launch + } + } + }, + releaseNote = latestRelease.body.toString(), + downloadStatus = currentDownloadStatus + ) + } +} + + +@Composable +fun UpdateDialog( + onDismissRequest: () -> Unit, + title: String, + onConfirmUpdate: () -> Unit, + releaseNote: String, + downloadStatus: UpdateUtil.DownloadStatus, +) { + AlertDialog( + onDismissRequest = {}, + title = { + Text(title) + + }, + icon = { Icon(Icons.Outlined.NewReleases, null) }, confirmButton = { + TextButton(onClick = { if (downloadStatus !is UpdateUtil.DownloadStatus.Progress) onConfirmUpdate() }) { + when (downloadStatus) { + is UpdateUtil.DownloadStatus.Progress -> Text("${downloadStatus.percent} %") + else -> Text(stringResource(R.string.update)) + } + } + }, dismissButton = { + DismissButton { onDismissRequest() } + }, text = { + Column(Modifier.verticalScroll(rememberScrollState())) { + Text(releaseNote) + } + }) +} + +@Composable +fun DismissButton(text: String = stringResource(R.string.dismiss), onClick: () -> Unit) { + TextButton(onClick = onClick) { + Text(text) + } } \ 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 6f71010c..20cb7375 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -175,4 +175,6 @@ Battery configuration Ignore system\'s battery optimization to be able to keep the app in the background. Dismiss + Update available + Update \ No newline at end of file diff --git a/app/src/main/res/xml/provider_paths.xml b/app/src/main/res/xml/provider_paths.xml new file mode 100644 index 00000000..791000e4 --- /dev/null +++ b/app/src/main/res/xml/provider_paths.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file From ad86c91c6ea2b0985d029d3ec55586b870add0f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Thu, 8 Dec 2022 14:39:01 +0100 Subject: [PATCH 31/39] Updated Spanish strings --- app/src/main/res/values-es/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index ec5b4a1e..2e4883ab 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -137,4 +137,7 @@ Parece que no tienes una cuenta de Spotify Premium. Configuración de la batería Ignora la optimización de batería del sistema para esta app para poder mantenerse en segundo plano. + Descartar + Actualización disponible + Actualizar \ No newline at end of file From aff60c4ca126c81d2d6656a0cee192516da03dd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Thu, 8 Dec 2022 16:56:54 +0100 Subject: [PATCH 32/39] fix: kotlinx serialization crashed the app --- app/build.gradle.kts | 2 +- app/proguard-rules.pro | 27 +++++++++++++++++++ .../itaysonlab/jetispot/ui/AppNavigation.kt | 2 ++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b9ac7a6d..0c4b85ac 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -9,7 +9,7 @@ plugins { id("dagger.hilt.android.plugin") id("kotlin-kapt") id("com.google.protobuf") version "0.8.18" - kotlin("plugin.serialization") version "1.4.21" + kotlin("plugin.serialization") version "1.7.21" } apply(plugin = "dagger.hilt.android.plugin") diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index a4ddc9ea..f0b13da3 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -48,6 +48,33 @@ # genericjson gson fixes -keep,allowobfuscation class * extends xyz.gianlu.librespot.json.JsonWrapper { *; } +# Keep `Companion` object fields of serializable classes. +# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects. +-if @kotlinx.serialization.Serializable class ** +-keepclassmembers class <1> { + static <1>$Companion Companion; +} + +# Keep `serializer()` on companion objects (both default and named) of serializable classes. +-if @kotlinx.serialization.Serializable class ** { + static **$* *; +} +-keepclassmembers class <2>$<3> { + kotlinx.serialization.KSerializer serializer(...); +} + +# Keep `INSTANCE.serializer()` of serializable objects. +-if @kotlinx.serialization.Serializable class ** { + public static ** INSTANCE; +} +-keepclassmembers class <1> { + public static <1> INSTANCE; + kotlinx.serialization.KSerializer serializer(...); +} + +# @Serializable and @Polymorphic are used at runtime for polymorphic serialization. +-keepattributes RuntimeVisibleAnnotations,AnnotationDefault + # protobuf -keep class com.spotify.** {*;} -keep class spotify.** {*;} 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 8002a9a6..1e68594a 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/AppNavigation.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/AppNavigation.kt @@ -96,6 +96,8 @@ fun AppNavigation( navController.navigate(if (sessionManager.isSignedIn()) Screen.Feed.route else Screen.Authorization.route) { popUpTo(Screen.NavGraph.route) } + } + LaunchedEffect(Unit){ launch(Dispatchers.IO) { kotlin.runCatching { val temp = UpdateUtil.checkForUpdate() From 80d557b476c9547578f9346d25b9b379d46fdbf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Fri, 9 Dec 2022 22:22:18 +0100 Subject: [PATCH 33/39] Changed "Choose an artist" string to app "R" denotation and fixed Hub shortcuts not being fully clickable (before adjusted to text) --- .../ui/dac/components_home/ShortcutsBinder.kt | 108 +++++++++++++----- app/src/main/res/values-es/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 3 files changed, 84 insertions(+), 26 deletions(-) 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 5e6eb6b4..20411f25 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 @@ -16,40 +16,96 @@ import com.spotify.home.dac.component.v1.proto.* @Composable fun ShortcutsBinder( - item: ShortcutsSectionComponent + item: ShortcutsSectionComponent ) { - 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)) { - pairs.forEachIndexed { xIdx, xItem -> - Box(Modifier.weight(1f).padding(end = if (xIdx == 0) 8.dp else 0.dp)) { - when (xItem) { - is AlbumCardShortcutComponent -> ShortcutComponentBinder(xItem.navigateUri, xItem.imageUri, "album", xItem.title) - is PlaylistCardShortcutComponent -> ShortcutComponentBinder(xItem.navigateUri, xItem.imageUri, "playlist", xItem.title) - is ShowCardShortcutComponent -> ShortcutComponentBinder(xItem.navigateUri, xItem.imageUri, "podcasts", xItem.title) - is ArtistCardShortcutComponent -> ShortcutComponentBinder(xItem.navigateUri, xItem.imageUri, "artist", xItem.title) - is EpisodeCardShortcutComponent -> ShortcutComponentBinder(xItem.navigateUri, xItem.imageUri, "podcasts", xItem.title) - } + 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) + ) { + pairs.forEachIndexed { xIdx, xItem -> + Box( + Modifier + .weight(1f) + .padding(end = if (xIdx == 0) 8.dp else 0.dp)) { + when (xItem) { + is AlbumCardShortcutComponent -> ShortcutComponentBinder( + xItem.navigateUri, + xItem.imageUri, + "album", + xItem.title + ) + is PlaylistCardShortcutComponent -> ShortcutComponentBinder( + xItem.navigateUri, + xItem.imageUri, + "playlist", + xItem.title + ) + is ShowCardShortcutComponent -> ShortcutComponentBinder( + xItem.navigateUri, + xItem.imageUri, + "podcasts", + xItem.title + ) + is ArtistCardShortcutComponent -> ShortcutComponentBinder( + xItem.navigateUri, + xItem.imageUri, + "artist", + xItem.title + ) + is EpisodeCardShortcutComponent -> ShortcutComponentBinder( + xItem.navigateUri, + xItem.imageUri, + "podcasts", + xItem.title + ) + } + } + } } - } } - } } @Composable private fun ShortcutComponentBinder( - navigateUri: String, - imageUrl: String, - imagePlaceholder: String, - title: String + navigateUri: String, + imageUrl: String, + imagePlaceholder: String, + title: String ) { - Card(colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.compositeSurfaceElevation(3.dp)), modifier = Modifier.height(56.dp).fillMaxWidth()) { - Row(Modifier.navClickable { navController -> - navController.navigate(navigateUri) - }) { - PreviewableAsyncImage(imageUrl = imageUrl, placeholderType = imagePlaceholder, modifier = Modifier.size(56.dp)) - Text(title, fontSize = 13.sp, lineHeight = 18.sp, maxLines = 2, overflow = TextOverflow.Ellipsis, modifier = Modifier.align( - Alignment.CenterVertically).padding(horizontal = 8.dp)) + Card( + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.compositeSurfaceElevation( + 3.dp + ) + ), modifier = Modifier + .height(56.dp) + .fillMaxWidth() + ) { + Row(Modifier.navClickable { navController -> + navController.navigate(navigateUri) + }) { + PreviewableAsyncImage( + imageUrl = imageUrl, + placeholderType = imagePlaceholder, + modifier = Modifier.size(56.dp) + ) + Text( + title, + fontSize = 13.sp, + lineHeight = 18.sp, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + modifier = Modifier + .align( + Alignment.CenterVertically + ) + .padding(horizontal = 8.dp) + .fillMaxWidth(), + + ) + } } - } } diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 2e4883ab..b9ee96ae 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -140,4 +140,5 @@ Descartar Actualización disponible Actualizar + Escoge un artista \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 20cb7375..eee90fed 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -177,4 +177,5 @@ Dismiss Update available Update + Choose an artist \ No newline at end of file From b123f0013d3978b1993acd932a07adffa08d7208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Fri, 9 Dec 2022 23:07:39 +0100 Subject: [PATCH 34/39] Fixed all "unsupported" id's in the Listening History. Made the login tokens appear in debug mode --- .../itaysonlab/jetispot/core/di/ApiModule.kt | 7 +++++++ .../itaysonlab/jetispot/core/objs/hub/HubComponent.kt | 8 +++++--- .../itaysonlab/jetispot/ui/hub/HubBinder.kt | 4 ++++ 3 files changed, 16 insertions(+), 3 deletions(-) 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 98710dd1..710c57de 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 @@ -1,5 +1,6 @@ package bruhcollective.itaysonlab.jetispot.core.di +import bruhcollective.itaysonlab.jetispot.BuildConfig import bruhcollective.itaysonlab.jetispot.core.SpSessionManager import bruhcollective.itaysonlab.jetispot.core.api.* import bruhcollective.itaysonlab.jetispot.core.di.ext.interceptRequest @@ -31,7 +32,13 @@ object ApiModule { interceptRequest { orig -> // 1. Authorization (& client token) header("Authorization", "Bearer ${sessionManager.session.tokens().get("playlist-read")}") + if(BuildConfig.DEBUG){ + Log.d("Authorization Bearer token", "Bearer ${sessionManager.session.tokens().get("playlist-read")}") + } header("client-token", tokenHandler.requestToken()) + if(BuildConfig.DEBUG){ + Log.d("Authorization Client Token", tokenHandler.requestToken()) + } // 2. Default headers header("User-Agent", "Spotify/${SpUtils.SPOTIFY_APP_VERSION} Android/32 (Pixel 4a (5G))") diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/objs/hub/HubComponent.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/objs/hub/HubComponent.kt index 21391280..58ea8c8c 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/objs/hub/HubComponent.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/objs/hub/HubComponent.kt @@ -26,11 +26,13 @@ sealed class HubComponent { @TypeLabel("artist:likedSongsRow") object ArtistLikedSongs: HubComponent() - @TypeLabel("listeninghistory:playlistContextRow") + @TypeLabel("listeninghistory:playlistContextRow", alternateLabels = ["listeninghistory:collectionContextRow", "listeninghistory:albumContextRow"]) object HistoryPlaylist: HubComponent() - // BROWSE + @TypeLabel("listeninghistory:dividerAfterPlaysFromContextRow") + object HistoryDivider: HubComponent() + // BROWSE @TypeLabel("find:categoryCard") object FindCard: HubComponent(), ComponentInGrid @@ -99,7 +101,7 @@ sealed class HubComponent { // IGNORING - @TypeLabel("listeninghistory:dividerAfterEntityRow") + @TypeLabel("listeninghistory:dividerAfterEntityRow", alternateLabels = ["listeninghistory:playsFromContextRow", "listeninghistory:artistContextRow" /*This by the moment*/]) object EmptySpace: HubComponent() @TypeLabel("freetier:offlineSwitchComponent", alternateLabels = ["find:header"]) 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 5f417cb5..2fb16473 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 @@ -2,6 +2,8 @@ package bruhcollective.itaysonlab.jetispot.ui.hub import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Divider import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -60,7 +62,9 @@ fun HubBinder ( HubComponent.PodcastTopics -> PodcastTopicsStrip(item) HubComponent.OutlinedButton -> OutlineButton(item) + HubComponent.HistoryPlaylist -> PlaylistTrackRowLarger(item) + HubComponent.HistoryDivider -> Divider(modifier = Modifier.padding(start = 14.dp, end = 14.dp).height(2.dp)) //TODO: Keep adding components searching them in the API (Thunder client) HubComponent.EmptySpace, HubComponent.Ignored -> {} From c834e9aeffc1b895c55b4229a11bdf7a07bb49fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Sat, 10 Dec 2022 10:23:17 +0100 Subject: [PATCH 35/39] Modified some files to merge this fork with the original project. --- README.md | 6 +----- .../itaysonlab/jetispot/core/util/UpdateUtil.kt | 4 ++-- .../jetispot/ui/screens/config/ConfigScreen.kt | 11 ++++++----- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 05311d7e..61232a12 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,6 @@ A Spotify unofficial client built with Jetpack Compose, Material You/3 and libre -## ⚠️ FORK -I don't want confusions so I'm telling this. This repository is just a fork of the one made by [iTaysonLab](https://github.com/iTaysonLab/jetispot) as I say in the credtis. At the moment of writing this, the project is kind of abandonated. By what I know, the original creator of the app is making an amazing Steam client for Android, Jetisteam. I will be adding new features to the app and also fixing bugs. If in some day Tayson want to go back to the project, I'll be glad to open a Pull Request to have all my work merged in it's original repository. Thanks for your comprehension! - ## 📣 NOTICE Spotify Premium account is **REQUIRED***. Offline caching, DRM bypassing or raw file downloading is prohibted by ToS and will NEVER be implemented in Jetispot. Don't waste your time trying to request these features. @@ -46,13 +43,12 @@ Spotify Premium account is **REQUIRED***. Offline caching, DRM bypassing or raw - pictures: Coil - storage: Room (collection), MMKV (metadata) - arch: MVVM -- preferences: Jetpack Datastore (proto) [maybe in a future MMKV will be used for some app variables] +- preferences: Jetpack Datastore (proto) ## ⬇️ Downloads You can go to the [releases page](https://github.com/BobbyESP/Jetispot/releases) and download any version updated. ## Credits -- [iTaysonLab](https://github.com/iTaysonLab) for creating this project. My fork is a continuation of it's work. If some day is interested in working back to the project, I'll be making a PR for merging changes. - [librespot-java](https://github.com/librespot-org/librespot-java) for the core API part and playback - [librespot-android](https://github.com/devgianlu/librespot-android) for sink and decoder source (in Jetispot they are rewritten to Kotlin) - [moshi](https://github.com/square/moshi/) and [moshix](https://github.com/ZacSweers/MoshiX/) for the undocumented API JSON parsing diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/UpdateUtil.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/UpdateUtil.kt index b24ec66b..e282033e 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/UpdateUtil.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/UpdateUtil.kt @@ -33,8 +33,8 @@ import kotlin.coroutines.suspendCoroutine object UpdateUtil { - private const val OWNER = "BobbyESP" - private const val REPO = "Jetispot" + private const val OWNER = "iTaysonLab" + private const val REPO = "jetispot" private const val ARM64 = "arm64-v8a" private const val ARM32 = "armeabi-v7a" private const val X86 = "x86" 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 541d991a..972540c6 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 @@ -86,9 +86,10 @@ class ConfigScreenViewModel @Inject constructor( add(ConfigItem.Preference(R.string.storage, { ctx, cfg -> "" }, { it.navigate(Screen.StorageConfig) })) - add(ConfigItem.Preference(R.string.language, { ctx, cfg -> "" }, { + + /*add(ConfigItem.Preference(R.string.language, { ctx, cfg -> "" }, { it.navigate(Screen.LanguageConfig) - })) + }))*/ add(ConfigItem.Category(R.string.config_account)) @@ -115,12 +116,12 @@ class ConfigScreenViewModel @Inject constructor( }, {})) add(ConfigItem.Preference(R.string.about_sources, { ctx, _ -> "" }, { - it.openInBrowser("https://github.com/BobbyESP/Jetispot") + it.openInBrowser("https://github.com/iTaysonLab/jetispot") })) - /*add(ConfigItem.Preference(R.string.about_channel, { ctx, _ -> "" }, { + add(ConfigItem.Preference(R.string.about_channel, { ctx, _ -> "" }, { it.openInBrowser("https://t.me/bruhcollective") - }))*/ + })) } override fun isRoot() = false From 543ab4692a5b952f4c64cb5fa1d515bcb8ab558a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Thu, 15 Dec 2022 17:08:43 +0100 Subject: [PATCH 36/39] Improved Light Mode experience (mostly player). Added support for large song names seeing --- app/build.gradle.kts | 2 +- .../jetispot/core/util/UpdateUtil.kt | 2 +- .../app_preferences/AppPreferencesUtil.kt | 38 +++++ .../nowplaying/NowPlayingMiniplayer.kt | 5 +- .../fullscreen/NowPlayingControls.kt | 53 +++--- .../NowPlayingFullscreenComposition.kt | 157 +++++++++-------- .../nowplaying/fullscreen/NowPlayingHeader.kt | 19 ++- .../fullscreen/NowPlayingLyricsComposition.kt | 5 +- .../fullscreen/NowPlayingLyricsContainer.kt | 20 ++- .../nowplaying/fullscreen/NowPlayingQueue.kt | 15 +- .../nowplaying/fullscreen/PlayerColorUtils.kt | 19 +++ .../jetispot/ui/shared/SharedTexts.kt | 161 +++++++++++++++++- 12 files changed, 381 insertions(+), 115 deletions(-) create mode 100644 app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/app_preferences/AppPreferencesUtil.kt create mode 100644 app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/PlayerColorUtils.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0c4b85ac..8d885c24 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -15,7 +15,7 @@ apply(plugin = "dagger.hilt.android.plugin") val versionMajor = 0 val versionMinor = 1 -val versionPatch = 4 +val versionPatch = 5 val versionBuild = 0 val isStable = true diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/UpdateUtil.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/UpdateUtil.kt index e282033e..fc121ac6 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/UpdateUtil.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/UpdateUtil.kt @@ -46,7 +46,7 @@ object UpdateUtil { Request.Builder().url("https://api.github.com/repos/${OWNER}/${REPO}/releases/latest") .build() private val jsonFormat = Json { ignoreUnknownKeys = true } - + private suspend fun getLatestRelease(): LatestRelease { return suspendCoroutine { continuation -> client.newCall(requestForLatestRelease).enqueue(object : Callback { diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/app_preferences/AppPreferencesUtil.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/app_preferences/AppPreferencesUtil.kt new file mode 100644 index 00000000..b992a190 --- /dev/null +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/app_preferences/AppPreferencesUtil.kt @@ -0,0 +1,38 @@ +package bruhcollective.itaysonlab.jetispot.core.util.app_preferences + +import android.content.Context +import android.content.SharedPreferences +import androidx.core.content.edit + +object AppPreferencesUtil { + private var sharedPreferences: SharedPreferences? = null + + fun setupConfig(context: Context) { + sharedPreferences = context.getSharedPreferences("jetispot.appconfig", + Context.MODE_PRIVATE + ) + } + + /* var Setting1: Float? + get() = Key./*Get the key*/./*function*/ + set(value) = /*Same*/ +*/ + + private enum class Key { + Setting1; + + fun getBoolean(defValue: Boolean = false, elseValue: Boolean? = null): Boolean? = if (sharedPreferences?.contains(name) == true) sharedPreferences!!.getBoolean(name, defValue) else elseValue + fun getFloat(defValue: Float = 0f, elseValue: Float? = null): Float? = if (sharedPreferences?.contains(name) == true) sharedPreferences!!.getFloat(name, defValue) else elseValue + fun getInt(defValue: Int = 0, elseValue: Int? = null): Int? = if (sharedPreferences?.contains(name) == true) sharedPreferences!!.getInt(name, defValue) else elseValue + fun getLong(defValue: Long = 0, elseValue: Long? = null): Long? = if (sharedPreferences?.contains(name) == true) sharedPreferences!!.getLong(name, defValue) else elseValue + fun getString(defValue: String = "", elseValue: String? = null): String? = if (sharedPreferences?.contains(name) == true) sharedPreferences!!.getString(name, defValue) else elseValue + + fun setBoolean(value: Boolean?) = value?.let { sharedPreferences!!.edit { putBoolean(name, value) } } ?: remove() + fun setFloat(value: Float?) = value?.let { sharedPreferences!!.edit { putFloat(name, value) } } ?: remove() + fun setInt(value: Int?) = value?.let { sharedPreferences!!.edit { putInt(name, value) } } ?: remove() + fun setLong(value: Long?) = value?.let { sharedPreferences!!.edit { putLong(name, value) } } ?: remove() + fun setString(value: String?) = value?.let { sharedPreferences!!.edit { putString(name, value) } } ?: remove() + + fun remove() = sharedPreferences!!.edit { remove(name) } + } +} \ No newline at end of file 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 13fc712f..e6a91091 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 @@ -20,6 +20,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import bruhcollective.itaysonlab.jetispot.R import bruhcollective.itaysonlab.jetispot.core.SpPlayerServiceManager +import bruhcollective.itaysonlab.jetispot.ui.shared.MarqueeText import bruhcollective.itaysonlab.jetispot.ui.shared.PlayPauseButton import bruhcollective.itaysonlab.jetispot.ui.shared.PreviewableSyncImage @@ -60,14 +61,14 @@ fun NowPlayingMiniplayer( .padding(horizontal = 14.dp) .align(Alignment.CenterVertically) ) { - Text( + MarqueeText( if (viewModel.currentTrack.value.title == "Unknown Title") stringResource(id = R.string.unknown_title) else viewModel.currentTrack.value.title, color = MaterialTheme.colorScheme.onSurface, maxLines = 1, overflow = TextOverflow.Ellipsis, fontSize = 16.sp ) - Text( + MarqueeText( if(viewModel.currentTrack.value.artist == "Unknown Artist") stringResource(id = R.string.unknown_artist) else viewModel.currentTrack.value.artist, color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), maxLines = 1, diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingControls.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingControls.kt index fed565b2..85fbf43c 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingControls.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingControls.kt @@ -3,6 +3,7 @@ package bruhcollective.itaysonlab.jetispot.ui.screens.nowplaying.fullscreen import android.text.format.DateUtils import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape @@ -15,21 +16,19 @@ import androidx.compose.material3.* import androidx.compose.runtime.* 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.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.positionInRoot +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import bruhcollective.itaysonlab.jetispot.core.SpPlayerServiceManager import bruhcollective.itaysonlab.jetispot.core.util.SpUtils import bruhcollective.itaysonlab.jetispot.ui.screens.nowplaying.NowPlayingViewModel -import bruhcollective.itaysonlab.jetispot.ui.shared.MediumText -import bruhcollective.itaysonlab.jetispot.ui.shared.PlayPauseButton -import bruhcollective.itaysonlab.jetispot.ui.shared.PreviewableAsyncImage -import bruhcollective.itaysonlab.jetispot.ui.shared.navClickable +import bruhcollective.itaysonlab.jetispot.ui.shared.* import com.spotify.metadata.Metadata import kotlinx.coroutines.CoroutineScope @@ -62,7 +61,9 @@ fun NowPlayingControls( private fun ControlsArtwork( viewModel: NowPlayingViewModel, ) { - ElevatedCard(modifier = Modifier.padding(horizontal = 14.dp).clip(RoundedCornerShape(12.dp))) { + ElevatedCard(modifier = Modifier + .padding(horizontal = 14.dp) + .clip(RoundedCornerShape(12.dp))) { PreviewableAsyncImage( imageUrl = remember(viewModel.currentTrack.value) { if (viewModel.currentQueue.value.isNotEmpty()) { @@ -86,7 +87,7 @@ private fun ControlsHeader( bottomSheetState: BottomSheetState, viewModel: NowPlayingViewModel, ) { - MediumText( + MarqueeText( text = viewModel.currentTrack.value.title, modifier = Modifier .padding(horizontal = 14.dp) @@ -95,13 +96,15 @@ private fun ControlsHeader( ) { navController -> viewModel.navigateToSource(scope, bottomSheetState, navController) }, - fontSize = 24.sp, color = Color.White, + fontSize = 24.sp, + fontWeight = FontWeight.Bold, ) Spacer(Modifier.height(2.dp)) - - Text(text = viewModel.currentTrack.value.artist, + + MarqueeText(text = viewModel.currentTrack.value.artist, modifier = Modifier + .alpha(0.7f) .padding(horizontal = 14.dp) .navClickable( enableRipple = false @@ -111,7 +114,6 @@ private fun ControlsHeader( maxLines = 1, overflow = TextOverflow.Ellipsis, fontSize = 16.sp, - color = Color.White.copy(alpha = 0.7f) ) } @@ -137,9 +139,9 @@ private fun ControlsSeekbar( } Slider(value = if (isSeekbarDragging) seekbarDraggingProgress else viewModel.currentPosition.value.progressRange, colors = SliderDefaults.colors( - thumbColor = Color.White, - activeTrackColor = Color.White, - inactiveTrackColor = Color.White.copy(alpha = 0.5f) + thumbColor = oppositeColorOfSystem(alpha = 1f), + activeTrackColor = oppositeColorOfSystem(alpha = 0.7f), + inactiveTrackColor = oppositeColorOfSystem(alpha = 0.35f) ), onValueChange = { isSeekbarDragging = true seekbarDraggingProgress = it @@ -155,14 +157,14 @@ private fun ControlsSeekbar( ) { Text( text = elapsedTime, - color = Color.White.copy(alpha = 0.7f), + modifier = Modifier.alpha(0.7f), fontSize = 12.sp ) Spacer(modifier = Modifier.weight(1f)) Text( text = totalTime, - color = Color.White.copy(alpha = 0.7f), - fontSize = 12.sp + fontSize = 12.sp, + modifier = Modifier.alpha(0.7f) ) } } @@ -174,17 +176,17 @@ private fun ControlsMainButtons( setQueueOpened: (Boolean) -> Unit, ) { Row(Modifier.padding(horizontal = 14.dp)) { - Surface(color = Color.White, modifier = Modifier + Surface(color = oppositeColorOfSystem(if (!isSystemInDarkTheme()) 0.75f else 1f), modifier = Modifier .clip(CircleShape) .clickable( interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(color = Color.Black) + indication = rememberRipple(color = systemThemeColor(alpha = 1f)) ) { viewModel.togglePlayPause() }) { PlayPauseButton( isPlaying = viewModel.currentState.value == SpPlayerServiceManager.PlaybackState.Playing, - color = Color.Black, + color = systemThemeColor(1f), modifier = Modifier .size(42.dp) .align(Alignment.CenterVertically) @@ -198,7 +200,7 @@ private fun ControlsMainButtons( modifier = Modifier .clip(CircleShape) .size(42.dp), - colors = IconButtonDefaults.iconButtonColors(containerColor = Color.White.copy(0.2f), contentColor = Color.White) + colors = IconButtonDefaults.iconButtonColors(containerColor = oppositeColorOfSystem(0.2f), contentColor = oppositeColorOfSystem(1f)) ) { Icon(imageVector = Icons.Rounded.SkipPrevious, contentDescription = null) } @@ -210,7 +212,7 @@ private fun ControlsMainButtons( modifier = Modifier .clip(CircleShape) .size(42.dp), - colors = IconButtonDefaults.iconButtonColors(containerColor = Color.White.copy(0.2f), contentColor = Color.White) + colors = IconButtonDefaults.iconButtonColors(containerColor = oppositeColorOfSystem(0.2f), contentColor = oppositeColorOfSystem(1f)) ) { Icon(imageVector = Icons.Rounded.SkipNext, contentDescription = null) } @@ -222,7 +224,7 @@ private fun ControlsMainButtons( modifier = Modifier .clip(CircleShape) .size(42.dp), - colors = IconButtonDefaults.iconButtonColors(containerColor = Color.White.copy(0.2f), contentColor = Color.White) + colors = IconButtonDefaults.iconButtonColors(containerColor = oppositeColorOfSystem(0.2f), contentColor = oppositeColorOfSystem(1f)) ) { Icon(imageVector = Icons.Rounded.FavoriteBorder, contentDescription = null) } @@ -237,9 +239,10 @@ private fun ControlsMainButtons( .onGloballyPositioned { coords -> viewModel.queueButtonParams = coords.positionInRoot() }, - colors = IconButtonDefaults.iconButtonColors(containerColor = Color.Transparent, contentColor = Color.White) + colors = IconButtonDefaults.iconButtonColors(containerColor = Color.Transparent, contentColor = oppositeColorOfSystem(1f)) ) { Icon(imageVector = Icons.Rounded.QueueMusic, contentDescription = null) } } -} \ No newline at end of file +} + 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 12480b47..cb78eccc 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 @@ -20,85 +20,102 @@ import kotlinx.coroutines.launch @OptIn(ExperimentalMaterialApi::class) @Composable -fun NowPlayingFullscreenComposition ( - queueOpened: Boolean, - setQueueOpened: (Boolean) -> Unit, - lyricsOpened: Boolean, - setLyricsOpened: (Boolean) -> Unit, - bottomSheetState: BottomSheetState, - viewModel: NowPlayingViewModel +fun NowPlayingFullscreenComposition( + queueOpened: Boolean, + setQueueOpened: (Boolean) -> Unit, + lyricsOpened: Boolean, + setLyricsOpened: (Boolean) -> Unit, + bottomSheetState: BottomSheetState, + viewModel: NowPlayingViewModel ) { - val scope = rememberCoroutineScope() + val scope = rememberCoroutineScope() - val queueProgress = animateFloatAsState(targetValue = if (queueOpened) 1f else 0f, animationSpec = spring(stiffness = 450f)) - val queueProgressValue = queueProgress.value - - val lyricsProgress = animateFloatAsState(targetValue = if (lyricsOpened) 1f else 0f, animationSpec = spring(stiffness = 450f)) - val lyricsProgressValue = lyricsProgress.value - - val anySuperProgress = remember(queueProgressValue, lyricsProgressValue) { - if (queueProgressValue > 0f) { - queueProgressValue - } else { - lyricsProgressValue - } - } + val queueProgress = animateFloatAsState( + targetValue = if (queueOpened) 1f else 0f, + animationSpec = spring(stiffness = 450f) + ) + val queueProgressValue = queueProgress.value - Box(modifier = Modifier.fillMaxSize()) { - NowPlayingBackground( - viewModel = viewModel, - modifier = Modifier.fillMaxSize(), + val lyricsProgress = animateFloatAsState( + targetValue = if (lyricsOpened) 1f else 0f, + animationSpec = spring(stiffness = 450f) ) + val lyricsProgressValue = lyricsProgress.value - // main content - NowPlayingHeader( - stateTitle = stringResource(id = viewModel.getHeaderTitle()), - onCloseClick = { - if (lyricsOpened) { - setLyricsOpened(false) - } else if (queueOpened) { - setQueueOpened(false) + val anySuperProgress = remember(queueProgressValue, lyricsProgressValue) { + if (queueProgressValue > 0f) { + queueProgressValue } else { - scope.launch { bottomSheetState.collapse() } + lyricsProgressValue } - }, - state = viewModel.getHeaderText(), - queueStateProgress = anySuperProgress, - modifier = Modifier - .statusBarsPadding() - .align(Alignment.TopCenter) - .fillMaxWidth() - .padding(horizontal = 16.dp) - ) + } - // composite + Box(modifier = Modifier.fillMaxSize()) { + NowPlayingBackground( + viewModel = viewModel, + modifier = Modifier.fillMaxSize(), + ) - if (anySuperProgress != 1f) { - NowPlayingControls( - scope = scope, viewModel = viewModel, bottomSheetState = bottomSheetState, queueOpened = queueOpened, setQueueOpened = setQueueOpened, lyricsOpened = lyricsOpened, setLyricsOpened = setLyricsOpened, modifier = Modifier - .disableTouch(disabled = queueOpened) - .alpha(1f - anySuperProgress) - .align(Alignment.BottomStart) - .fillMaxHeight() - .padding(horizontal = 8.dp) - .padding(bottom = 24.dp) - .navigationBarsPadding() - .offset { - IntOffset(x = 0, y = (-(48).dp.toPx() * (anySuperProgress)).toInt()) - } - ) - } + // main content + NowPlayingHeader( + stateTitle = stringResource(id = viewModel.getHeaderTitle()), + onCloseClick = { + if (lyricsOpened) { + setLyricsOpened(false) + } else if (queueOpened) { + setQueueOpened(false) + } else { + scope.launch { bottomSheetState.collapse() } + } + }, + state = viewModel.getHeaderText(), + queueStateProgress = anySuperProgress, + modifier = Modifier + .statusBarsPadding() + .align(Alignment.TopCenter) + .fillMaxWidth() + .padding(horizontal = 16.dp) + ) - NowPlayingQueue( - viewModel = viewModel, - modifier = Modifier.fillMaxSize().alpha(1f - lyricsProgressValue), - rvStateProgress = queueProgressValue - ) + // composite - NowPlayingLyricsComposition( - viewModel = viewModel, - modifier = Modifier.fillMaxSize().alpha(1f - queueProgressValue), - rvStateProgress = lyricsProgressValue - ) - } + if (anySuperProgress != 1f) { + NowPlayingControls( + scope = scope, + viewModel = viewModel, + bottomSheetState = bottomSheetState, + queueOpened = queueOpened, + setQueueOpened = setQueueOpened, + lyricsOpened = lyricsOpened, + setLyricsOpened = setLyricsOpened, + modifier = Modifier + .disableTouch(disabled = queueOpened) + .alpha(1f - anySuperProgress) + .align(Alignment.BottomStart) + .fillMaxHeight() + .padding(horizontal = 8.dp) + .padding(bottom = 24.dp) + .navigationBarsPadding() + .offset { + IntOffset(x = 0, y = (-(48).dp.toPx() * (anySuperProgress)).toInt()) + } + ) + } + + NowPlayingQueue( + viewModel = viewModel, + modifier = Modifier + .fillMaxSize() + .alpha(1f - lyricsProgressValue), + rvStateProgress = queueProgressValue + ) + + NowPlayingLyricsComposition( + viewModel = viewModel, + modifier = Modifier + .fillMaxSize() + .alpha(1f - queueProgressValue), + rvStateProgress = lyricsProgressValue + ) + } } \ No newline at end of file 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 8bd86f8b..4b74a5c1 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 @@ -31,18 +31,25 @@ fun NowPlayingHeader( ) { Row(modifier, verticalAlignment = Alignment.CenterVertically) { IconButton(onClick = onCloseClick, Modifier.size(32.dp)) { - Icon(imageVector = Icons.Rounded.KeyboardArrowDown, tint = Color.White, contentDescription = null, modifier = Modifier.scale(1f - queueStateProgress).alpha(1f - queueStateProgress)) - Icon(imageVector = Icons.Rounded.Close, tint = Color.White, contentDescription = null, modifier = Modifier.scale(queueStateProgress).alpha(queueStateProgress)) + Icon(imageVector = Icons.Rounded.KeyboardArrowDown, contentDescription = null, modifier = Modifier + .scale(1f - queueStateProgress) + .alpha(1f - queueStateProgress)) + Icon(imageVector = Icons.Rounded.Close, contentDescription = null, modifier = Modifier + .scale(queueStateProgress) + .alpha(queueStateProgress)) } - Column(Modifier.weight(1f).padding(vertical = 8.dp)) { + Column( + Modifier + .weight(1f) + .padding(vertical = 8.dp)) { Text( text = stateTitle.uppercase(), modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), textAlign = TextAlign.Center, - color = Color.White.copy(alpha = 0.7f), + color = oppositeColorOfSystem(alpha = 0.7f), maxLines = 1, overflow = TextOverflow.Ellipsis, letterSpacing = 2.sp, @@ -54,7 +61,7 @@ fun NowPlayingHeader( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), - color = Color.White, + color = oppositeColorOfSystem(alpha = 1f), maxLines = 1, overflow = TextOverflow.Ellipsis, textAlign = TextAlign.Center, @@ -64,7 +71,7 @@ fun NowPlayingHeader( } IconButton(onClick = { /*TODO*/ }, Modifier.size(32.dp)) { - Icon(imageVector = Icons.Rounded.MoreVert, tint = Color.White, contentDescription = null) + Icon(imageVector = Icons.Rounded.MoreVert, tint = oppositeColorOfSystem(alpha = 1f), contentDescription = null) } } } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingLyricsComposition.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingLyricsComposition.kt index 36be8c8c..36af651d 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingLyricsComposition.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingLyricsComposition.kt @@ -39,6 +39,7 @@ fun NowPlayingLyricsComposition( rvStateProgress: Float ) { Box(modifier) { + val color = oppositeColorOfSystem(alpha = 0.2f) Canvas(modifier = Modifier.fillMaxSize()) { val offset = Offset( x = lerp(viewModel.lyricsCardParams.first.x, 0f, rvStateProgress), @@ -61,7 +62,7 @@ fun NowPlayingLyricsComposition( val radius = androidx.compose.ui.unit.lerp(12.dp, 0.dp, rvStateProgress).toPx() drawRoundRect( - color = Color.White.copy(alpha = 0.2f), + color = color, topLeft = offset, size = size, cornerRadius = CornerRadius(radius, radius) @@ -99,7 +100,7 @@ fun NowPlayingLyricsComposition( items(viewModel.spLyricsController.currentLyricsLines) { line -> Text( text = line.words, - color = Color.White, + color = oppositeColorOfSystem(alpha = 1f), fontSize = 18.sp, fontWeight = FontWeight.SemiBold ) diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingLyricsContainer.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingLyricsContainer.kt index 54e2318b..d1e07020 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingLyricsContainer.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingLyricsContainer.kt @@ -46,13 +46,27 @@ fun NowPlayingLyricsContainer( Row(verticalAlignment = Alignment.CenterVertically) { Icon(Icons.Rounded.Lyrics, contentDescription = null, modifier = Modifier.size(16.dp)) Spacer(modifier = Modifier.width(8.dp)) - Text(text = "Lyrics", color = Color.White.copy(alpha = 0.7f), letterSpacing = 2.sp, fontSize = 13.sp) + Text( + text = "Lyrics", + color = oppositeColorOfSystem(alpha = 0.7f), + letterSpacing = 2.sp, + fontSize = 13.sp + ) Spacer(modifier = Modifier.weight(1f)) - Icon(Icons.Rounded.Fullscreen, contentDescription = null, modifier = Modifier.size(16.dp)) + Icon( + Icons.Rounded.Fullscreen, + contentDescription = null, + modifier = Modifier.size(16.dp) + ) } Spacer(modifier = Modifier.height(4.dp)) - Text(text = viewModel.spLyricsController.currentSongLine, color = Color.White, fontSize = 18.sp, fontWeight = FontWeight.SemiBold) + Text( + text = viewModel.spLyricsController.currentSongLine, + color = oppositeColorOfSystem(alpha = 1f), + fontSize = 18.sp, + fontWeight = FontWeight.SemiBold + ) } } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingQueue.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingQueue.kt index 35ee294f..fb48173c 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingQueue.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/NowPlayingQueue.kt @@ -32,6 +32,10 @@ fun NowPlayingQueue( rvStateProgress: Float ) { Box(modifier) { + + //return the color using oppositeSystemColor function + val color = oppositeColorOfSystem(alpha = 0.2f) + Canvas(modifier = Modifier.fillMaxSize()) { val offsetPx = 4.dp.toPx() val sizePx = 40.dp.toPx() @@ -49,7 +53,7 @@ fun NowPlayingQueue( val radius = lerp(36.dp, 0.dp, rvStateProgress).toPx() drawRoundRect( - color = Color.White.copy(alpha = 0.2f), + color = color, topLeft = offset, size = size, cornerRadius = CornerRadius(radius, radius) @@ -67,7 +71,12 @@ fun NowPlayingQueue( IntOffset(x = 0, y = (48.dp.toPx() * (1f - rvStateProgress)).toInt()) }) { - LazyColumn(contentPadding = PaddingValues(bottom = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding())) { + LazyColumn( + contentPadding = PaddingValues( + bottom = WindowInsets.navigationBars.asPaddingValues() + .calculateBottomPadding() + ) + ) { items(viewModel.currentQueue.value) { queueItem -> QueueItem(queueItem) } @@ -105,7 +114,7 @@ private fun QueueItem( ) { MediumText(item.name, fontWeight = FontWeight.Normal) Spacer(modifier = Modifier.height(4.dp)) - Subtext(item.artistList.joinToString(", ") { it.name }, maxLines = 1,) + Subtext(item.artistList.joinToString(", ") { it.name }, maxLines = 1) } } } diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/PlayerColorUtils.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/PlayerColorUtils.kt new file mode 100644 index 00000000..5098cda2 --- /dev/null +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/fullscreen/PlayerColorUtils.kt @@ -0,0 +1,19 @@ +package bruhcollective.itaysonlab.jetispot.ui.screens.nowplaying.fullscreen + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color + +//return color black if system theme is light and white if system theme is dark +@Composable +fun oppositeColorOfSystem(alpha : Float): Color { + val isSystemInDarkTheme = isSystemInDarkTheme() + return if (isSystemInDarkTheme) Color.White.copy(alpha = alpha) else Color.Black.copy(alpha = alpha) +} + +//return the color of the system theme +@Composable +fun systemThemeColor(alpha : Float): Color { + val isSystemInDarkTheme = isSystemInDarkTheme() + return if (isSystemInDarkTheme) Color.Black.copy(alpha = alpha) else Color.White.copy(alpha = alpha) +} \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/shared/SharedTexts.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/shared/SharedTexts.kt index aedd9d1f..f4812af6 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/shared/SharedTexts.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/shared/SharedTexts.kt @@ -1,15 +1,31 @@ package bruhcollective.itaysonlab.jetispot.ui.shared +import androidx.compose.animation.core.* +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.Placeable +import androidx.compose.ui.layout.SubcomposeLayout +import androidx.compose.ui.text.PlatformTextStyle +import androidx.compose.ui.text.TextLayoutResult +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import kotlinx.coroutines.delay @Composable fun MediumText ( @@ -42,4 +58,145 @@ fun SubtextOverline ( modifier: Modifier = Modifier ) { Text(text, color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), letterSpacing = 2.sp, fontSize = 12.sp, lineHeight = 18.sp, maxLines = 2, overflow = TextOverflow.Ellipsis, modifier = modifier) -} \ No newline at end of file +} + +@Composable +fun MarqueeText( + text: String, + modifier: Modifier = Modifier, + textModifier: Modifier = Modifier, + color: Color = Color.Unspecified, + fontSize: TextUnit = TextUnit.Unspecified, + fontStyle: FontStyle? = null, + fontWeight: FontWeight? = null, + fontFamily: FontFamily? = null, + letterSpacing: TextUnit = TextUnit.Unspecified, + textDecoration: TextDecoration? = null, + textAlign: TextAlign? = null, + lineHeight: TextUnit = TextUnit.Unspecified, + maxLines: Int = 1, + overflow: TextOverflow = TextOverflow.Clip, + softWrap: Boolean = true, + onTextLayout: (TextLayoutResult) -> Unit = {}, + style: TextStyle = LocalTextStyle.current.plus(TextStyle(platformStyle = PlatformTextStyle(includeFontPadding = false))), + sideGradientColor: Color = Color.Transparent, + basicGradientColor: Color = Color.Transparent, +) { + val createText = @Composable { localModifier: Modifier -> + Text( + text, + textAlign = textAlign, + modifier = localModifier, + color = color, + fontSize = fontSize, + fontStyle = fontStyle, + fontWeight = fontWeight, + fontFamily = fontFamily, + letterSpacing = letterSpacing, + textDecoration = textDecoration, + lineHeight = lineHeight, + overflow = overflow, + softWrap = softWrap, + maxLines = 1, + onTextLayout = onTextLayout, + style = style, + ) + } + var offset by remember { mutableStateOf(0) } + val textLayoutInfoState = remember { mutableStateOf(null) } + LaunchedEffect(textLayoutInfoState.value) { + val textLayoutInfo = textLayoutInfoState.value ?: return@LaunchedEffect + if (textLayoutInfo.textWidth <= textLayoutInfo.containerWidth) return@LaunchedEffect + val duration = 7500 * textLayoutInfo.textWidth / textLayoutInfo.containerWidth + val delay = 500L + do { + val animation = TargetBasedAnimation( + animationSpec = infiniteRepeatable( + animation = tween( + durationMillis = duration, + delayMillis = 1000, + easing = LinearEasing, + ), + repeatMode = RepeatMode.Restart + ), + typeConverter = Int.VectorConverter, + initialValue = 0, + targetValue = -textLayoutInfo.textWidth + ) + val startTime = withFrameNanos { it } + do { + val playTime = withFrameNanos { it } - startTime + offset = (animation.getValueFromNanos(playTime)) + } while (!animation.isFinishedFromNanos(playTime)) + delay(delay) + } while (true) + } + + SubcomposeLayout( + modifier = modifier.clipToBounds() + ) { constraints -> + val infiniteWidthConstraints = constraints.copy(maxWidth = Int.MAX_VALUE) + var mainText = subcompose(MarqueeLayers.MainText) { + createText(textModifier) + }.first().measure(infiniteWidthConstraints) + + var gradient: Placeable? = null + + var secondPlaceableWithOffset: Pair? = null + if (mainText.width <= constraints.maxWidth) { + offset = 0 + textLayoutInfoState.value = null + } else { + val spacing = constraints.maxWidth * 2 / 3 + textLayoutInfoState.value = TextLayoutInfo( + textWidth = mainText.width + spacing, + containerWidth = constraints.maxWidth + ) + val secondTextOffset = mainText.width + offset + spacing + val secondTextSpace = constraints.maxWidth - secondTextOffset + if (secondTextSpace > 0) { + secondPlaceableWithOffset = subcompose(MarqueeLayers.SecondaryText) { + createText(textModifier) + }.first().measure(infiniteWidthConstraints) to secondTextOffset + } + gradient = subcompose(MarqueeLayers.EdgesGradient) { + Row { + GradientEdge(basicGradientColor, sideGradientColor) + Spacer(Modifier.weight(1f)) + GradientEdge(sideGradientColor, basicGradientColor) + } + }.first().measure(constraints.copy(maxHeight = mainText.height)) + } + + layout( + width = if (mainText.width > constraints.maxWidth) constraints.maxWidth else mainText.width, + height = mainText.height + ) { + mainText.place(offset, 0) + secondPlaceableWithOffset?.let { + it.first.place(it.second, 0) + } + gradient?.place(0, 0) + } + } +} + +@Composable +private fun GradientEdge( + startColor: Color, endColor: Color, +) { + Box( + modifier = Modifier + .width(10.dp) + .fillMaxHeight() + .background( + brush = Brush.horizontalGradient( + listOf(startColor, endColor) + ) + ) + ) +} + + +private enum class MarqueeLayers { MainText, SecondaryText, EdgesGradient } +private data class TextLayoutInfo(val textWidth: Int, val containerWidth: Int) \ No newline at end of file From a5eb95949708383c457b05d5869553493ac4dffe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Thu, 15 Dec 2022 18:07:59 +0100 Subject: [PATCH 37/39] Added a little transition based in the miniplayer offset. --- .../ui/screens/nowplaying/NowPlayingScreen.kt | 3 ++- .../fullscreen/NowPlayingFullscreenComposition.kt | 11 +++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) 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 fc82a90d..d8320d8b 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 @@ -37,7 +37,8 @@ fun NowPlayingScreen( lyricsOpened = lyricsOpened, setLyricsOpened = setLyricsOpened, bottomSheetState = bottomSheetState, - viewModel = viewModel + viewModel = viewModel, + bsOffset = bsOffset() ) NowPlayingMiniplayer( 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 cb78eccc..91a0cd3b 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 @@ -2,9 +2,11 @@ package bruhcollective.itaysonlab.jetispot.ui.screens.nowplaying.fullscreen import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.spring +import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.BottomSheetState import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -14,8 +16,10 @@ import androidx.compose.ui.draw.alpha import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.IntOffset 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) @@ -26,6 +30,7 @@ fun NowPlayingFullscreenComposition( lyricsOpened: Boolean, setLyricsOpened: (Boolean) -> Unit, bottomSheetState: BottomSheetState, + bsOffset: Float, viewModel: NowPlayingViewModel ) { val scope = rememberCoroutineScope() @@ -49,11 +54,13 @@ fun NowPlayingFullscreenComposition( lyricsProgressValue } } + Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.compositeSurfaceElevation( + 3.dp + ))) { - Box(modifier = Modifier.fillMaxSize()) { NowPlayingBackground( viewModel = viewModel, - modifier = Modifier.fillMaxSize(), + modifier = Modifier.fillMaxSize().alpha(1f * bsOffset), ) // main content From f52cbf6534560e154fee9b47a7512756043d3db3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Sun, 18 Dec 2022 15:08:14 +0100 Subject: [PATCH 38/39] Started implementing a "More Options" bottom sheet dialog. --- .../itaysonlab/jetispot/MainActivity.kt | 5 + .../jetispot/core/metadata_db/SpMetadataDb.kt | 2 +- .../itaysonlab/jetispot/core/util/SpUtils.kt | 2 - .../itaysonlab/jetispot/ui/AppNavigation.kt | 11 ++ .../ui/bottomsheets/MoreOptionsBottomSheet.kt | 163 ++++++++++++++++++ .../jump_to_artist/JumpToArtistBottomSheet.kt | 117 ++++++++----- .../itaysonlab/jetispot/ui/screens/Screen.kt | 3 +- .../screens/nowplaying/NowPlayingViewModel.kt | 10 ++ .../NowPlayingFullscreenComposition.kt | 61 ++++--- .../nowplaying/fullscreen/NowPlayingHeader.kt | 122 ++++++++----- app/src/main/res/values/strings.xml | 4 + 11 files changed, 385 insertions(+), 115 deletions(-) create mode 100644 app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/bottomsheets/MoreOptionsBottomSheet.kt diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/MainActivity.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/MainActivity.kt index 47dbbd46..3b562d88 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/MainActivity.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/MainActivity.kt @@ -89,6 +89,11 @@ class MainActivity : ComponentActivity() { val backPressedDispatcherOwner = LocalOnBackPressedDispatcherOwner.current // remembers val scope = rememberCoroutineScope() + + //q: can a remember bottom sheet scaffold state be used for multiple bottom sheets? + //a: yes, but you need to use the same scaffold state for all of them + + //In conclusion, you can have multiple bottom sheets, but you can only have one bottom sheet scaffold state val bsState = rememberBottomSheetScaffoldState() val bottomSheetNavigator = rememberBottomSheetNavigator() diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/metadata_db/SpMetadataDb.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/metadata_db/SpMetadataDb.kt index 5c002bc7..d44c80ec 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/metadata_db/SpMetadataDb.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/metadata_db/SpMetadataDb.kt @@ -15,7 +15,7 @@ class SpMetadataDb @Inject constructor( fun contains(uri: String) = instance.containsKey(uri) - fun get(uri: String): ByteArray = instance.getBytes(uri, null) + fun get(uri: String): ByteArray = instance.getBytes(uri, null)!! fun put(uri: String, msg: ByteArray) = instance.encode(uri, msg) fun clear() = instance.clearAll() diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/SpUtils.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/SpUtils.kt index a2dd3b19..82763f45 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/SpUtils.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/SpUtils.kt @@ -26,6 +26,4 @@ object SpUtils { fun getScannableUrl(uri: String) = "https://scannables.scdn.co/uri/800/$uri" fun getImageUrl(bytes: ByteString?) = if (bytes != null) "https://i.scdn.co/image/${Utils.bytesToHex(bytes).lowercase()}" else null - //check if it's premium account - } \ No newline at end of file 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 1e68594a..2e649e39 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/AppNavigation.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/AppNavigation.kt @@ -33,6 +33,7 @@ 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.UpdateUtil +import bruhcollective.itaysonlab.jetispot.ui.bottomsheets.MoreOptionsBottomSheet import bruhcollective.itaysonlab.jetispot.ui.bottomsheets.jump_to_artist.JumpToArtistBottomSheet import bruhcollective.itaysonlab.jetispot.ui.screens.BottomSheet import bruhcollective.itaysonlab.jetispot.ui.screens.Dialog @@ -44,6 +45,7 @@ import bruhcollective.itaysonlab.jetispot.ui.screens.config.QualityConfigScreen import bruhcollective.itaysonlab.jetispot.ui.screens.config.StorageScreen import bruhcollective.itaysonlab.jetispot.ui.screens.dac.DacRendererScreen import bruhcollective.itaysonlab.jetispot.ui.screens.dynamic.DynamicSpIdScreen +import bruhcollective.itaysonlab.jetispot.ui.screens.nowplaying.NowPlayingViewModel import bruhcollective.itaysonlab.jetispot.ui.screens.search.SearchScreen import bruhcollective.itaysonlab.jetispot.ui.screens.yourlibrary2.YourLibraryContainerScreen import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi @@ -203,10 +205,19 @@ fun AppNavigation( }) } + //Bottom Sheet Dialogs + bottomSheet(BottomSheet.JumpToArtist.route) { entry -> val data = remember { entry.arguments!!.getString("artistIdsAndRoles")!! } JumpToArtistBottomSheet(data = data) } + + bottomSheet(BottomSheet.MoreOptions.route) { entry -> + val trackName = remember {entry.arguments!!.getString("trackName")!!} + val artistName = remember {entry.arguments!!.getString("artistName")!!} + val artworkUrl = remember {entry.arguments!!.getString("artworkUrl")!!} + MoreOptionsBottomSheet(trackName = trackName, artistName = artistName, artworkUrl = artworkUrl) + } } if (showUpdateDialog) { diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/bottomsheets/MoreOptionsBottomSheet.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/bottomsheets/MoreOptionsBottomSheet.kt new file mode 100644 index 00000000..1b5aeb3a --- /dev/null +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/bottomsheets/MoreOptionsBottomSheet.kt @@ -0,0 +1,163 @@ +package bruhcollective.itaysonlab.jetispot.ui.bottomsheets + +import android.graphics.drawable.Icon +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Star +import androidx.compose.material.icons.rounded.Person +import androidx.compose.material.icons.rounded.Share +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import bruhcollective.itaysonlab.jetispot.R +import bruhcollective.itaysonlab.jetispot.core.util.SpUtils +import bruhcollective.itaysonlab.jetispot.ui.ext.compositeSurfaceElevation +import bruhcollective.itaysonlab.jetispot.ui.screens.nowplaying.NowPlayingViewModel +import bruhcollective.itaysonlab.jetispot.ui.shared.MarqueeText +import bruhcollective.itaysonlab.jetispot.ui.shared.PreviewableAsyncImage +import com.spotify.metadata.Metadata + +@Composable +fun MoreOptionsBottomSheet( + trackName: String, + artistName: String, + artworkUrl: String, +) { + val decodedUrl = "https://i.scdn.co/image/${artworkUrl}" + Column( + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.compositeSurfaceElevation(8.dp)) + .navigationBarsPadding() + ) { + Divider( + modifier = Modifier + .width(32.dp) + .padding(vertical = 14.dp) + .clip(CircleShape) + .align(Alignment.CenterHorizontally), + thickness = 4.dp, + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(0.4f) + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = stringResource(id = R.string.more_options), + fontSize = 22.sp, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp) + ) + + Row( + modifier = Modifier.padding(top = 8.dp, start = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + ElevatedCard(modifier = Modifier.clip(RoundedCornerShape(8.dp)), elevation = CardDefaults.cardElevation(8.dp)) { + PreviewableAsyncImage( + imageUrl = decodedUrl, + placeholderType = "track", + modifier = Modifier.size(64.dp) + ) + } + Column(modifier = Modifier.padding(start = 16.dp)) { + MarqueeText( + text = trackName, + color = MaterialTheme.colorScheme.onSurface, + fontSize = 18.sp, + fontWeight = FontWeight.Bold + ) + MarqueeText( + text = artistName, + color = MaterialTheme.colorScheme.onSurface.copy(0.7f) + ) + } + } + Spacer(modifier = Modifier.height(16.dp)) + Divider( + modifier = Modifier + .clip(CircleShape) + .align(Alignment.CenterHorizontally) + .padding(horizontal = 12.dp), + thickness = 3.dp, + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(0.4f) + ) + Column(modifier = Modifier) { + actionButton( + icon = Icons.Rounded.Person, + text = stringResource(id = R.string.go_to_artist), + onClick = {}) + buttonsDivider() + actionButton( + icon = Icons.Filled.Star, + text = stringResource(id = R.string.save_to_your_music), + onClick = {}) + buttonsDivider() + actionButton( + icon = Icons.Rounded.Share, + text = stringResource(id = R.string.share), + onClick = {}) + } + } +} + +@Composable +private fun actionButton( + icon: ImageVector, + text: String, + onClick: () -> Unit +) { + Box(modifier = Modifier.padding()) { + Row( + modifier = Modifier + .clickable(onClick = onClick) + .fillMaxWidth() + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = icon, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier + .size(24.dp) + ) + Text( + text = text, + color = MaterialTheme.colorScheme.onSurface, + fontSize = 14.sp, + modifier = Modifier.padding(start = 16.dp) + ) + } + } +} + +@Composable +private fun buttonsDivider() { + Divider( + modifier = Modifier.padding(horizontal = 16.dp), + thickness = 1.dp, + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(0.4f) + ) +} + +@Preview +@Composable +fun actionButtonPreview() { + actionButton(icon = Icons.Default.Star, text = "Go to song artist", onClick = {}) +} \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/bottomsheets/jump_to_artist/JumpToArtistBottomSheet.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/bottomsheets/jump_to_artist/JumpToArtistBottomSheet.kt index 9cbbf338..b42b207d 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/bottomsheets/jump_to_artist/JumpToArtistBottomSheet.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/bottomsheets/jump_to_artist/JumpToArtistBottomSheet.kt @@ -5,14 +5,20 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Divider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import bruhcollective.itaysonlab.jetispot.R @@ -25,54 +31,83 @@ import xyz.gianlu.librespot.metadata.ArtistId @Composable fun JumpToArtistBottomSheet( - data: String + data: String ) { - val navController = LocalNavigationController.current + val navController = LocalNavigationController.current - val content = remember { - data.split("|").map { - Metadata.ArtistWithRole.parseFrom(Utils.hexToBytes(it)) - }.map { - Triple( - ArtistId.fromHex(Utils.bytesToHex(it.artistGid)).toSpotifyUri(), - it.artistName, - when (it.role) { - Metadata.ArtistWithRole.ArtistRole.ARTIST_ROLE_MAIN_ARTIST -> R.string.artist_role_main - Metadata.ArtistWithRole.ArtistRole.ARTIST_ROLE_FEATURED_ARTIST -> R.string.artist_role_feat - Metadata.ArtistWithRole.ArtistRole.ARTIST_ROLE_REMIXER -> R.string.artist_role_remixer - Metadata.ArtistWithRole.ArtistRole.ARTIST_ROLE_ACTOR -> R.string.artist_role_actor - Metadata.ArtistWithRole.ArtistRole.ARTIST_ROLE_COMPOSER -> R.string.artist_role_composer - Metadata.ArtistWithRole.ArtistRole.ARTIST_ROLE_CONDUCTOR -> R.string.artist_role_conductor - Metadata.ArtistWithRole.ArtistRole.ARTIST_ROLE_ORCHESTRA -> R.string.artist_role_orchestra - else -> R.string.artist_role_unknown + val content = remember { + data.split("|").map { + Metadata.ArtistWithRole.parseFrom(Utils.hexToBytes(it)) + }.map { + Triple( + ArtistId.fromHex(Utils.bytesToHex(it.artistGid)).toSpotifyUri(), + it.artistName, + when (it.role) { + Metadata.ArtistWithRole.ArtistRole.ARTIST_ROLE_MAIN_ARTIST -> R.string.artist_role_main + Metadata.ArtistWithRole.ArtistRole.ARTIST_ROLE_FEATURED_ARTIST -> R.string.artist_role_feat + Metadata.ArtistWithRole.ArtistRole.ARTIST_ROLE_REMIXER -> R.string.artist_role_remixer + Metadata.ArtistWithRole.ArtistRole.ARTIST_ROLE_ACTOR -> R.string.artist_role_actor + Metadata.ArtistWithRole.ArtistRole.ARTIST_ROLE_COMPOSER -> R.string.artist_role_composer + Metadata.ArtistWithRole.ArtistRole.ARTIST_ROLE_CONDUCTOR -> R.string.artist_role_conductor + Metadata.ArtistWithRole.ArtistRole.ARTIST_ROLE_ORCHESTRA -> R.string.artist_role_orchestra + else -> R.string.artist_role_unknown + } + ) } - ) } - } - Column( - Modifier - .background(MaterialTheme.colorScheme.compositeSurfaceElevation(8.dp)) - .fillMaxWidth() - .clip(RoundedCornerShape(8.dp)) - .navigationBarsPadding()) { - MediumText(text = "Choose an artist", fontSize = 21.sp, color = MaterialTheme.colorScheme.onSurface, modifier = Modifier.padding(horizontal = 16.dp).padding(top = 16.dp, bottom = 8.dp)) + Column( + Modifier + .background(MaterialTheme.colorScheme.compositeSurfaceElevation(8.dp)) + .fillMaxWidth() + .navigationBarsPadding() + ) { + Divider( + modifier = Modifier + .width(32.dp) + .padding(vertical = 14.dp) + .clip(CircleShape) + .align(Alignment.CenterHorizontally), + thickness = 4.dp, + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(0.4f) + ) + + Text( + text = stringResource(id = R.string.choose_artist), + fontSize = 22.sp, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp) + ) - LazyColumn { - items(content) { artist -> - Column( - Modifier - .clickable { - navController.popBackStack() - navController.navigate(artist.first) - } - .padding(horizontal = 16.dp, vertical = 8.dp) - .fillMaxWidth()) { - MediumText(text = artist.second, color = MaterialTheme.colorScheme.onSurface) - Spacer(modifier = Modifier.height(2.dp)) - Text(text = stringResource(id = artist.third), maxLines = 1, color = MaterialTheme.colorScheme.onSurface) + LazyColumn { + items(content) { artist -> + Column( + Modifier + .clickable { + navController.popBackStack() + navController.navigate(artist.first) + } + .padding(horizontal = 16.dp, vertical = 8.dp) + .fillMaxWidth() + ) { + Text( + text = artist.second, + fontSize = 18.sp, + color = MaterialTheme.colorScheme.onSurface + ) + Spacer(modifier = Modifier.height(2.dp)) + Text( + text = stringResource(id = artist.third), + fontSize = 16.sp, + maxLines = 1, + color = MaterialTheme.colorScheme.onSurface.copy(0.7f) + ) + } } } } - } } \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/Screen.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/Screen.kt index 1f3c8369..17687ffb 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/Screen.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/Screen.kt @@ -56,5 +56,6 @@ enum class Dialog( enum class BottomSheet( val route: String ) { - JumpToArtist("bs/jumpToArtist/{artistIdsAndRoles}") // ID=ROLE|ID=ROLE + JumpToArtist("bs/jumpToArtist/{artistIdsAndRoles}"), // ID=ROLE|ID=ROLE + MoreOptions("bs/moreOptions/{trackName}/{artistName}/{artworkUrl}") //TODO: ADD ARGUMENTS } diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/NowPlayingViewModel.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/NowPlayingViewModel.kt index 114e771d..79aa86cc 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/NowPlayingViewModel.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/NowPlayingViewModel.kt @@ -15,6 +15,7 @@ import bruhcollective.itaysonlab.jetispot.SpApp import bruhcollective.itaysonlab.jetispot.core.SpPlayerServiceManager import bruhcollective.itaysonlab.jetispot.core.api.SpPartnersApi import bruhcollective.itaysonlab.jetispot.core.lyrics.SpLyricsController +import bruhcollective.itaysonlab.jetispot.core.util.Log import bruhcollective.itaysonlab.jetispot.core.util.SpUtils import bruhcollective.itaysonlab.jetispot.ui.ext.blendWith import bruhcollective.itaysonlab.jetispot.ui.navigation.NavigationController @@ -66,6 +67,15 @@ class NowPlayingViewModel @Inject constructor( navigationController.navigate(currentContextUri.value) } + @OptIn(ExperimentalMaterialApi::class) + fun navigateToMoreOptions(navigationController: NavigationController, scope: CoroutineScope, bottomSheetState: BottomSheetState) { + navigationController.navigate(BottomSheet.MoreOptions, mapOf( + "trackName" to currentTrack.value.title, + "artistName" to currentTrack.value.artist, + "artworkUrl" to Utils.bytesToHex(getCurrentTrackAsMetadata().album.coverGroup.imageList[0].fileId).lowercase(), + )) + } + @OptIn(ExperimentalMaterialApi::class) fun navigateToArtist(scope: CoroutineScope, sheetState: BottomSheetState, navigationController: NavigationController) { scope.launch { sheetState.collapse() } 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 91a0cd3b..2a0f2285 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 @@ -1,7 +1,10 @@ package bruhcollective.itaysonlab.jetispot.ui.screens.nowplaying.fullscreen +import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState 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.material.BottomSheetState @@ -54,36 +57,46 @@ fun NowPlayingFullscreenComposition( lyricsProgressValue } } - Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.compositeSurfaceElevation( - 3.dp - ))) { + Box(modifier = Modifier + .fillMaxSize() + .background( + MaterialTheme.colorScheme.compositeSurfaceElevation( + 3.dp + ) + )) { NowPlayingBackground( viewModel = viewModel, - modifier = Modifier.fillMaxSize().alpha(1f * bsOffset), - ) - - // main content - NowPlayingHeader( - stateTitle = stringResource(id = viewModel.getHeaderTitle()), - onCloseClick = { - if (lyricsOpened) { - setLyricsOpened(false) - } else if (queueOpened) { - setQueueOpened(false) - } else { - scope.launch { bottomSheetState.collapse() } - } - }, - state = viewModel.getHeaderText(), - queueStateProgress = anySuperProgress, modifier = Modifier - .statusBarsPadding() - .align(Alignment.TopCenter) - .fillMaxWidth() - .padding(horizontal = 16.dp) + .fillMaxSize() + .alpha(1f * bsOffset), ) + // main content + AnimatedVisibility(visible = bsOffset >= 0.99f, enter = fadeIn(), exit = fadeOut()) { + NowPlayingHeader( + stateTitle = stringResource(id = viewModel.getHeaderTitle()), + onCloseClick = { + if (lyricsOpened) { + setLyricsOpened(false) + } else if (queueOpened) { + setQueueOpened(false) + } else { + scope.launch { bottomSheetState.collapse() } + } + }, + state = viewModel.getHeaderText(), + queueStateProgress = anySuperProgress, + modifier = Modifier + .statusBarsPadding() + .align(Alignment.TopCenter) + .fillMaxWidth() + .padding(horizontal = 16.dp), + viewModel = viewModel, + bottomSheetState = bottomSheetState, + scope = scope, + ) + } // composite if (anySuperProgress != 1f) { 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 4b74a5c1..c4b7d6a9 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,6 +1,9 @@ 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.material.ExperimentalMaterialApi import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Close import androidx.compose.material.icons.rounded.KeyboardArrowDown @@ -12,6 +15,7 @@ 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 @@ -20,58 +24,84 @@ 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 fun NowPlayingHeader( - stateTitle: String, - state: String, - queueStateProgress: Float, - onCloseClick: () -> Unit, - modifier: Modifier + stateTitle: String, + state: String, + queueStateProgress: Float, + onCloseClick: () -> Unit, + modifier: Modifier, + viewModel: NowPlayingViewModel, + bottomSheetState: BottomSheetState, + scope: CoroutineScope ) { - Row(modifier, verticalAlignment = Alignment.CenterVertically) { - IconButton(onClick = onCloseClick, Modifier.size(32.dp)) { - Icon(imageVector = Icons.Rounded.KeyboardArrowDown, contentDescription = null, modifier = Modifier - .scale(1f - queueStateProgress) - .alpha(1f - queueStateProgress)) - Icon(imageVector = Icons.Rounded.Close, contentDescription = null, modifier = Modifier - .scale(queueStateProgress) - .alpha(queueStateProgress)) - } + Row(modifier, verticalAlignment = Alignment.CenterVertically) { + IconButton(onClick = onCloseClick, Modifier.size(32.dp)) { + Icon( + imageVector = Icons.Rounded.KeyboardArrowDown, + contentDescription = null, + modifier = Modifier + .scale(1f - queueStateProgress) + .alpha(1f - queueStateProgress) + ) + Icon( + imageVector = Icons.Rounded.Close, contentDescription = null, modifier = Modifier + .scale(queueStateProgress) + .alpha(queueStateProgress) + ) + } - Column( - Modifier - .weight(1f) - .padding(vertical = 8.dp)) { - Text( - text = stateTitle.uppercase(), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - textAlign = TextAlign.Center, - color = oppositeColorOfSystem(alpha = 0.7f), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - letterSpacing = 2.sp, - fontSize = 10.sp - ) + Column( + Modifier + .weight(1f) + .padding(vertical = 8.dp) + ) { + Text( + text = stateTitle.uppercase(), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + textAlign = TextAlign.Center, + color = oppositeColorOfSystem(alpha = 0.7f), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + letterSpacing = 2.sp, + fontSize = 10.sp + ) - Text( - text = state, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - color = oppositeColorOfSystem(alpha = 1f), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - textAlign = TextAlign.Center, - fontWeight = FontWeight.Bold, - fontSize = 14.sp - ) - } + Text( + text = state, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + color = oppositeColorOfSystem(alpha = 1f), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Center, + fontWeight = FontWeight.Bold, + fontSize = 14.sp + ) + } - IconButton(onClick = { /*TODO*/ }, Modifier.size(32.dp)) { - Icon(imageVector = Icons.Rounded.MoreVert, tint = oppositeColorOfSystem(alpha = 1f), contentDescription = null) + Column( + Modifier + .size(32.dp) + .navClickable { navController -> + viewModel.navigateToMoreOptions(navigationController = navController, bottomSheetState = bottomSheetState, scope = scope) + }, + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Icon( + imageVector = Icons.Rounded.MoreVert, + tint = oppositeColorOfSystem(alpha = 1f), + contentDescription = null + ) + } } - } } \ 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 eee90fed..7cb9b900 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -178,4 +178,8 @@ Update available Update Choose an artist + More options + Go to the artist\'s profile + Add this song to favourites + Share this track \ No newline at end of file From 0cdbd634774ce0dd2159290271c621323b94a37c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Font=C3=A1n?= Date: Mon, 19 Dec 2022 00:06:39 +0100 Subject: [PATCH 39/39] Almost finished the "new" miniplayer UI and changed a string --- .../itaysonlab/jetispot/MainActivity.kt | 23 +- .../util/helpers/MoreOptionsDialogHelper.kt | 9 + .../itaysonlab/jetispot/ui/AppNavigation.kt | 4 +- .../ui/bottomsheets/MoreOptionsBottomSheet.kt | 19 +- .../itaysonlab/jetispot/ui/screens/Screen.kt | 2 +- .../nowplaying/NowPlayingMiniplayer.kt | 223 +++++++++++++++++- .../ui/screens/nowplaying/NowPlayingScreen.kt | 68 +++--- .../screens/nowplaying/NowPlayingViewModel.kt | 3 +- app/src/main/res/values/strings.xml | 2 +- 9 files changed, 304 insertions(+), 49 deletions(-) create mode 100644 app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/helpers/MoreOptionsDialogHelper.kt diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/MainActivity.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/MainActivity.kt index 3b562d88..f98b5d2d 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/MainActivity.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/MainActivity.kt @@ -12,6 +12,7 @@ import androidx.activity.compose.setContent import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.background import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.BottomSheetScaffold import androidx.compose.material.BottomSheetValue import androidx.compose.material.ExperimentalMaterialApi @@ -19,6 +20,8 @@ import androidx.compose.material.rememberBottomSheetScaffoldState import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp @@ -212,19 +215,21 @@ class MainActivity : ComponentActivity() { ) { innerPadding -> BottomSheetScaffold( sheetContent = { - NowPlayingScreen( - bottomSheetState = bsState.bottomSheetState, - bsOffset = bsOffset, - queueOpened = bsQueueOpened, - setQueueOpened = { bsQueueOpened = it }, - lyricsOpened = bsLyricsOpened, - setLyricsOpened = { bsLyricsOpened = it } - ) + NowPlayingScreen( + bottomSheetState = bsState.bottomSheetState, + bsOffset = bsOffset, + queueOpened = bsQueueOpened, + setQueueOpened = { bsQueueOpened = it }, + lyricsOpened = bsLyricsOpened, + setLyricsOpened = { bsLyricsOpened = it } + ) }, scaffoldState = bsState, sheetPeekHeight = bsPeek, backgroundColor = MaterialTheme.colorScheme.surface, - sheetGesturesEnabled = !bsQueueOpened && !bsLyricsOpened + sheetGesturesEnabled = !bsQueueOpened && !bsLyricsOpened, + sheetShape = RoundedCornerShape(36.dp * (1f - bsOffset())), + modifier = Modifier.background(Color.Transparent) ) { innerScaffoldPadding -> AppNavigation( navController = navController, diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/helpers/MoreOptionsDialogHelper.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/helpers/MoreOptionsDialogHelper.kt new file mode 100644 index 00000000..f7cd36bd --- /dev/null +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/core/util/helpers/MoreOptionsDialogHelper.kt @@ -0,0 +1,9 @@ +package bruhcollective.itaysonlab.jetispot.core.util.helpers + +import bruhcollective.itaysonlab.jetispot.ui.screens.nowplaying.NowPlayingViewModel + +class MoreOptionsDialogHelper( + private val nowPlayingViewModel: NowPlayingViewModel +) { + +} \ No newline at end of file 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 2e649e39..d7c3ee7c 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/AppNavigation.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/AppNavigation.kt @@ -45,7 +45,6 @@ import bruhcollective.itaysonlab.jetispot.ui.screens.config.QualityConfigScreen import bruhcollective.itaysonlab.jetispot.ui.screens.config.StorageScreen import bruhcollective.itaysonlab.jetispot.ui.screens.dac.DacRendererScreen import bruhcollective.itaysonlab.jetispot.ui.screens.dynamic.DynamicSpIdScreen -import bruhcollective.itaysonlab.jetispot.ui.screens.nowplaying.NowPlayingViewModel import bruhcollective.itaysonlab.jetispot.ui.screens.search.SearchScreen import bruhcollective.itaysonlab.jetispot.ui.screens.yourlibrary2.YourLibraryContainerScreen import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi @@ -216,7 +215,8 @@ fun AppNavigation( val trackName = remember {entry.arguments!!.getString("trackName")!!} val artistName = remember {entry.arguments!!.getString("artistName")!!} val artworkUrl = remember {entry.arguments!!.getString("artworkUrl")!!} - MoreOptionsBottomSheet(trackName = trackName, artistName = artistName, artworkUrl = artworkUrl) + val artistsData = remember {entry.arguments!!.getString("artistsData")!!} + MoreOptionsBottomSheet(trackName, artistName, artworkUrl, artistsData) } } diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/bottomsheets/MoreOptionsBottomSheet.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/bottomsheets/MoreOptionsBottomSheet.kt index 1b5aeb3a..e7eec57c 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/bottomsheets/MoreOptionsBottomSheet.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/bottomsheets/MoreOptionsBottomSheet.kt @@ -24,18 +24,26 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import bruhcollective.itaysonlab.jetispot.R import bruhcollective.itaysonlab.jetispot.core.util.SpUtils +import bruhcollective.itaysonlab.jetispot.core.util.helpers.MoreOptionsDialogHelper import bruhcollective.itaysonlab.jetispot.ui.ext.compositeSurfaceElevation +import bruhcollective.itaysonlab.jetispot.ui.navigation.LocalNavigationController +import bruhcollective.itaysonlab.jetispot.ui.screens.BottomSheet import bruhcollective.itaysonlab.jetispot.ui.screens.nowplaying.NowPlayingViewModel import bruhcollective.itaysonlab.jetispot.ui.shared.MarqueeText import bruhcollective.itaysonlab.jetispot.ui.shared.PreviewableAsyncImage import com.spotify.metadata.Metadata +import xyz.gianlu.librespot.common.Utils +import xyz.gianlu.librespot.metadata.ArtistId @Composable fun MoreOptionsBottomSheet( trackName: String, artistName: String, artworkUrl: String, + artistsData: String, ) { + val navController = LocalNavigationController.current + val decodedUrl = "https://i.scdn.co/image/${artworkUrl}" Column( modifier = Modifier @@ -75,16 +83,18 @@ fun MoreOptionsBottomSheet( modifier = Modifier.size(64.dp) ) } - Column(modifier = Modifier.padding(start = 16.dp)) { + Column(modifier = Modifier.padding(start = 16.dp, end = 8.dp)) { MarqueeText( text = trackName, color = MaterialTheme.colorScheme.onSurface, fontSize = 18.sp, fontWeight = FontWeight.Bold ) + Spacer(modifier = Modifier.height(4.dp)) MarqueeText( text = artistName, - color = MaterialTheme.colorScheme.onSurface.copy(0.7f) + color = MaterialTheme.colorScheme.onSurface.copy(0.7f), + fontSize = 12.sp, ) } } @@ -101,7 +111,10 @@ fun MoreOptionsBottomSheet( actionButton( icon = Icons.Rounded.Person, text = stringResource(id = R.string.go_to_artist), - onClick = {}) + onClick = { + navController.navigate(BottomSheet.JumpToArtist, mapOf( + "artistIdsAndRoles" to artistsData + ))}) buttonsDivider() actionButton( icon = Icons.Filled.Star, diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/Screen.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/Screen.kt index 17687ffb..3bd524a8 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/Screen.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/Screen.kt @@ -57,5 +57,5 @@ enum class BottomSheet( val route: String ) { JumpToArtist("bs/jumpToArtist/{artistIdsAndRoles}"), // ID=ROLE|ID=ROLE - MoreOptions("bs/moreOptions/{trackName}/{artistName}/{artworkUrl}") //TODO: ADD ARGUMENTS + MoreOptions("bs/moreOptions/{trackName}/{artistName}/{artworkUrl}/{artistsData}") //TODO: ADD ARGUMENTS } 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 e6a91091..1d19163b 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 @@ -5,6 +5,7 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme @@ -15,6 +16,7 @@ import androidx.compose.ui.Alignment 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.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -24,6 +26,220 @@ import bruhcollective.itaysonlab.jetispot.ui.shared.MarqueeText import bruhcollective.itaysonlab.jetispot.ui.shared.PlayPauseButton import bruhcollective.itaysonlab.jetispot.ui.shared.PreviewableSyncImage +@Composable +fun NowPlayingMiniplayer2( + viewModel: NowPlayingViewModel, + modifier: Modifier, + visible: Boolean, + bsOffset: Float, +) { + AnimatedVisibility(visible = visible, enter = fadeIn(), exit = fadeOut()) { + Surface(tonalElevation = 8.dp, modifier = modifier) { + Box(Modifier.fillMaxSize()) { + Row( + Modifier + .fillMaxHeight(0.98f) + .padding(horizontal = 16.dp) + ) { + PreviewableSyncImage( + viewModel.currentTrack.value.artworkCompose, + placeholderType = "track", + modifier = Modifier + .size(48.dp) + .align(Alignment.CenterVertically) + .clip(RoundedCornerShape(8.dp)) + ) + Column( + modifier = Modifier + .align(Alignment.CenterVertically) + .fillMaxWidth() + .padding(start = 16.dp) + ) { + MarqueeText( + if (viewModel.currentTrack.value.title == "Unknown Title") stringResource( + id = R.string.unknown_title + ) else viewModel.currentTrack.value.title, + color = MaterialTheme.colorScheme.onSurface, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) + MarqueeText( + if (viewModel.currentTrack.value.artist == "Unknown Artist") stringResource( + id = R.string.unknown_artist + ) else viewModel.currentTrack.value.artist, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontSize = 12.sp, + ) + } + + } + LinearProgressIndicator( + progress = viewModel.currentPosition.value.progressRange, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier + .height(2.dp) + .fillMaxWidth() + .align(Alignment.BottomCenter) + ) + } + } + } +} + + @Composable + fun NowPlayingMiniplayer( + viewModel: NowPlayingViewModel, + modifier: Modifier, + visible: Boolean, + bsOffset: Float, + ) { + AnimatedVisibility(visible = visible, enter = fadeIn(), exit = fadeOut()) { + Surface(tonalElevation = 8.dp, modifier = modifier) { + Box(Modifier.fillMaxSize()) { + LinearProgressIndicator( + progress = viewModel.currentPosition.value.progressRange, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier + .height(2.dp) + .fillMaxWidth() + .align(Alignment.BottomCenter) + ) + Row( + Modifier + .fillMaxHeight() + .padding(horizontal = 16.dp) + ) { + PreviewableSyncImage( + viewModel.currentTrack.value.artworkCompose, + placeholderType = "track", + modifier = Modifier + .size(48.dp) + .align(Alignment.CenterVertically) + .clip(RoundedCornerShape(8.dp)) + ) + + Column( + Modifier + .weight(2f) + .padding(horizontal = 14.dp) + .align(Alignment.CenterVertically) + ) { + MarqueeText( + if (viewModel.currentTrack.value.title == "Unknown Title") stringResource( + id = R.string.unknown_title + ) else viewModel.currentTrack.value.title, + color = MaterialTheme.colorScheme.onSurface, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) + MarqueeText( + if (viewModel.currentTrack.value.artist == "Unknown Artist") stringResource( + id = R.string.unknown_artist + ) else viewModel.currentTrack.value.artist, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontSize = 12.sp, + ) + } + Surface( + modifier = Modifier + .width(64.dp) + .fillMaxHeight() + .padding(vertical = 12.dp), + color = MaterialTheme.colorScheme.onPrimaryContainer.copy(0.1f), + shape = CircleShape + ) { + PlayPauseButton( + viewModel.currentState.value == SpPlayerServiceManager.PlaybackState.Playing, + MaterialTheme.colorScheme.onSecondaryContainer.copy(0.85f), + Modifier + .width(56.dp) + .align(Alignment.CenterVertically) + .clickable { viewModel.togglePlayPause() } + ) + } + } + } + } + } + } + +/* + Row( + Modifier + .fillMaxHeight(0.98f) + .padding(horizontal = 16.dp) + ) { + PreviewableSyncImage( + viewModel.currentTrack.value.artworkCompose, + placeholderType = "track", + modifier = Modifier + .size(48.dp) + .align(Alignment.CenterVertically) + .clip(RoundedCornerShape(8.dp)) + ) + Column( + modifier = Modifier + .align(Alignment.CenterVertically) + .fillMaxWidth() + .padding(start = 16.dp) + ) { + MarqueeText( + if (viewModel.currentTrack.value.title == "Unknown Title") stringResource( + id = R.string.unknown_title + ) else viewModel.currentTrack.value.title, + color = MaterialTheme.colorScheme.onSurface, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) + MarqueeText( + if (viewModel.currentTrack.value.artist == "Unknown Artist") stringResource( + id = R.string.unknown_artist + ) else viewModel.currentTrack.value.artist, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontSize = 12.sp, + ) + } + Row( + modifier = Modifier.align(Alignment.CenterVertically), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically + ) { + Surface( + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f), + shape = CircleShape, + modifier = Modifier + .align(Alignment.CenterVertically) + .padding(start = 16.dp) + .size(32.dp), + ) { + PlayPauseButton( + viewModel.currentState.value == SpPlayerServiceManager.PlaybackState.Playing, + MaterialTheme.colorScheme.onSecondaryContainer.copy(0.85f), + Modifier + .width(56.dp) + .align(Alignment.CenterVertically) + .clickable { viewModel.togglePlayPause() } + ) + } + } + } + */ + +// ------------------------------------------------------------------------------------ + +/* ORIGINAL @Composable fun NowPlayingMiniplayer( viewModel: NowPlayingViewModel, @@ -61,14 +277,14 @@ fun NowPlayingMiniplayer( .padding(horizontal = 14.dp) .align(Alignment.CenterVertically) ) { - MarqueeText( + Text( if (viewModel.currentTrack.value.title == "Unknown Title") stringResource(id = R.string.unknown_title) else viewModel.currentTrack.value.title, color = MaterialTheme.colorScheme.onSurface, maxLines = 1, overflow = TextOverflow.Ellipsis, fontSize = 16.sp ) - MarqueeText( + Text( if(viewModel.currentTrack.value.artist == "Unknown Artist") stringResource(id = R.string.unknown_artist) else viewModel.currentTrack.value.artist, color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), maxLines = 1, @@ -93,4 +309,5 @@ fun NowPlayingMiniplayer( } } } -} \ No newline at end of file +} +*/ \ No newline at end of file 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 d8320d8b..3e5261b6 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 @@ -1,10 +1,13 @@ package bruhcollective.itaysonlab.jetispot.ui.screens.nowplaying +import androidx.compose.foundation.background import androidx.compose.foundation.clickable 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.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.BottomSheetState import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.runtime.Composable @@ -12,6 +15,8 @@ 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 import bruhcollective.itaysonlab.jetispot.ui.screens.nowplaying.fullscreen.NowPlayingFullscreenComposition @@ -20,36 +25,41 @@ import kotlinx.coroutines.launch @Composable @OptIn(ExperimentalMaterialApi::class) fun NowPlayingScreen( - bottomSheetState: BottomSheetState, - bsOffset: () -> Float, - queueOpened: Boolean, - setQueueOpened: (Boolean) -> Unit, - lyricsOpened: Boolean, - setLyricsOpened: (Boolean) -> Unit, - viewModel: NowPlayingViewModel = hiltViewModel() + bottomSheetState: BottomSheetState, + bsOffset: () -> Float, + queueOpened: Boolean, + setQueueOpened: (Boolean) -> Unit, + lyricsOpened: Boolean, + setLyricsOpened: (Boolean) -> Unit, + viewModel: NowPlayingViewModel = hiltViewModel() ) { - val scope = rememberCoroutineScope() + val scope = rememberCoroutineScope() - Box(Modifier.fillMaxSize()) { - NowPlayingFullscreenComposition( - queueOpened = queueOpened, - setQueueOpened = setQueueOpened, - lyricsOpened = lyricsOpened, - setLyricsOpened = setLyricsOpened, - bottomSheetState = bottomSheetState, - viewModel = viewModel, - bsOffset = bsOffset() - ) + Box( + Modifier + .fillMaxSize() + .background(Color.Transparent) + ) { + NowPlayingFullscreenComposition( + queueOpened = queueOpened, + setQueueOpened = setQueueOpened, + lyricsOpened = lyricsOpened, + setLyricsOpened = setLyricsOpened, + bottomSheetState = bottomSheetState, + viewModel = viewModel, + bsOffset = bsOffset() + ) - NowPlayingMiniplayer( - viewModel, - Modifier - .alpha(1f - bsOffset()) - .clickable { scope.launch { bottomSheetState.expand() } } - .fillMaxWidth() - .height(72.dp) - .align(Alignment.TopStart), - visible = bsOffset() <= 0.99f - ) - } + NowPlayingMiniplayer( + viewModel, + Modifier + .alpha(1f - bsOffset()) + .clickable { scope.launch { bottomSheetState.expand() } } + .fillMaxWidth() + .height(72.dp) + .align(Alignment.TopStart), + visible = bsOffset() <= 0.99f, + bsOffset = bsOffset() + ) + } } diff --git a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/NowPlayingViewModel.kt b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/NowPlayingViewModel.kt index 79aa86cc..1d4bd566 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/NowPlayingViewModel.kt +++ b/app/src/main/java/bruhcollective/itaysonlab/jetispot/ui/screens/nowplaying/NowPlayingViewModel.kt @@ -72,7 +72,8 @@ class NowPlayingViewModel @Inject constructor( navigationController.navigate(BottomSheet.MoreOptions, mapOf( "trackName" to currentTrack.value.title, "artistName" to currentTrack.value.artist, - "artworkUrl" to Utils.bytesToHex(getCurrentTrackAsMetadata().album.coverGroup.imageList[0].fileId).lowercase(), + "artworkUrl" to Utils.bytesToHex(getCurrentTrackAsMetadata().album.coverGroup.imageList[0].fileId).lowercase(), // <-- INFO: THIS IS NOT THE FULL URL. Just the ID of it. Passing a full URL will cause a crash because we can't use them in a DeepLink Navigation URL. + "artistsData" to getCurrentTrackAsMetadata().artistWithRoleList.joinToString("|") { Utils.bytesToHex(it.toByteString()) } )) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7cb9b900..27ee9ece 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -180,6 +180,6 @@ Choose an artist More options Go to the artist\'s profile - Add this song to favourites + Add this song to favorites Share this track \ No newline at end of file