Compare commits

...

5 Commits

Author SHA1 Message Date
Kingkor Roy Tirtho
bba725a139 fix(android): fully close the app on swipe-away and avoid duplicate cookie DataStore
Initialize NewPipeDownloader once per process so recreating the Activity
no longer opens a second DataStore on spotube_cookies.preferences_pb.
Kill the process after PlaybackService shuts down on task removal so the
proxy server, MQTT client and singletons don't linger in the background.
2026-10-30 13:35:39 +06:00
Kingkor Roy Tirtho
c058074cff fix(plugin): update default selected plugins to use built-in plugins with distinct abilities 2026-10-30 13:08:34 +06:00
Kingkor Roy Tirtho
2aaa0db81e fix(broker): update default broker settings and improve connection error handling 2026-10-30 11:50:39 +06:00
Kingkor Roy Tirtho
14bc279653 fix(ios): crashing on search screen 2026-10-30 11:10:25 +06:00
Kingkor Roy Tirtho
2c1148a126 chore: remove devil's name 2026-10-30 10:33:11 +06:00
32 changed files with 136 additions and 99 deletions

View File

@ -290,8 +290,8 @@
- **desktop**: double titlebar in local library folders and massive space in - **desktop**: double titlebar in local library folders and massive space in
overlay player overlay player
- lastfm form broken in other locales #2447 - lastfm form broken in other locales #2447
- spotify login broken due to new totp requirement #2494 - Greedy Green Corp login broken due to new totp requirement #2494
- spotify authentication 429 errors - Greedy Green Corp authentication 429 errors
### Features ### Features
@ -326,7 +326,7 @@
- **android**: home widget support (#2148) - **android**: home widget support (#2148)
- rewrite entire app in shadcn-ui replacing material eww - rewrite entire app in shadcn-ui replacing material eww
- grid/list customizable playbutton view - grid/list customizable playbutton view
- flag to hide spotify generated images with patterns - flag to hide Greedy Green Corp generated images with patterns
- show placeholder images where there is no item or on empty page - show placeholder images where there is no item or on empty page
- pause playback when no internet connection - pause playback when no internet connection
- implement yt-dlp for desktop and NewPipeExtractor for Android (#2316) - implement yt-dlp for desktop and NewPipeExtractor for Android (#2316)
@ -552,7 +552,7 @@
[#1463](https://github.com/team-spotube/spotube/issues/1463) [#1463](https://github.com/team-spotube/spotube/issues/1463)
[#1505](https://github.com/team-spotube/spotube/issues/1505) [#1505](https://github.com/team-spotube/spotube/issues/1505)
([d2683c5](https://github.com/team-spotube/spotube/commit/d2683c52d81d807be6ff72f15b8e9eb18181e211)) ([d2683c5](https://github.com/team-spotube/spotube/commit/d2683c52d81d807be6ff72f15b8e9eb18181e211))
- spotify friends and user profile icon (mobile) showing when not authenticated - Greedy Green Corp friends and user profile icon (mobile) showing when not authenticated
[#1410](https://github.com/team-spotube/spotube/issues/1410) [#1410](https://github.com/team-spotube/spotube/issues/1410)
([9bccbc9](https://github.com/team-spotube/spotube/commit/9bccbc93c63dd34f6e15ff68c276976ecd1d9a33)) ([9bccbc9](https://github.com/team-spotube/spotube/commit/9bccbc93c63dd34f6e15ff68c276976ecd1d9a33))
- **updater:** dead link - **updater:** dead link
@ -576,7 +576,7 @@
### Features ### Features
- add Spotify homepage personalized recommendations - add Greedy Green Corp homepage personalized recommendations
([#1402](https://github.com/team-spotube/spotube/issues/1402)) ([#1402](https://github.com/team-spotube/spotube/issues/1402))
([9e25c74](https://github.com/team-spotube/spotube/commit/9e25c742d4e43e4e10d2b48afb8e6d90288ffa11)) ([9e25c74](https://github.com/team-spotube/spotube/commit/9e25c742d4e43e4e10d2b48afb8e6d90288ffa11))
- add user profile page - add user profile page
@ -684,7 +684,7 @@
- add create playlist button in add playlist dialog - add create playlist button in add playlist dialog
([2168a64](https://personal.github.com/team-spotube/spotube/commit/2168a640af3104a43139c303d78e2c2326a1bda7)) ([2168a64](https://personal.github.com/team-spotube/spotube/commit/2168a640af3104a43139c303d78e2c2326a1bda7))
- add spotify friends activity - add Greedy Green Corp friends activity
([#1130](https://personal.github.com/team-spotube/spotube/issues/1130)) ([#1130](https://personal.github.com/team-spotube/spotube/issues/1130))
([7983932](https://personal.github.com/team-spotube/spotube/commit/79839329b0970acccb0c566a31eee508adbc8557)) ([7983932](https://personal.github.com/team-spotube/spotube/commit/79839329b0970acccb0c566a31eee508adbc8557))
- **deep-link:** add track opening page - **deep-link:** add track opening page
@ -1436,7 +1436,7 @@
([448c9b3](https://github.com/team-spotube/spotube/commit/448c9b39f407668ad92a695afe3c9741baeca20d)) ([448c9b3](https://github.com/team-spotube/spotube/commit/448c9b39f407668ad92a695afe3c9741baeca20d))
- **macos:** crashing on startup - **macos:** crashing on startup
([c46b428](https://github.com/team-spotube/spotube/commit/c46b4284b1d46a614cbcebc8c2f2e52714921b9b)) ([c46b428](https://github.com/team-spotube/spotube/commit/c46b4284b1d46a614cbcebc8c2f2e52714921b9b))
- spotify query hooks overriding default query params - Greedy Green Corp query hooks overriding default query params
([ec9a02e](https://github.com/team-spotube/spotube/commit/ec9a02e8b8d988e15ed58027054d2a9090d98873)) ([ec9a02e](https://github.com/team-spotube/spotube/commit/ec9a02e8b8d988e15ed58027054d2a9090d98873))
## [2.7.0](https://github.com/team-spotube/spotube/compare/v2.6.0...v2.7.0) (2023-03-07) ## [2.7.0](https://github.com/team-spotube/spotube/compare/v2.6.0...v2.7.0) (2023-03-07)
@ -1469,7 +1469,7 @@
([9080441](https://github.com/team-spotube/spotube/commit/9080441b875ceb91260bbad79291365a98d5be95)) ([9080441](https://github.com/team-spotube/spotube/commit/9080441b875ceb91260bbad79291365a98d5be95))
- individual shuffle and repeat/loop button of player - individual shuffle and repeat/loop button of player
([f79223c](https://github.com/team-spotube/spotube/commit/f79223cd41c61d9836d25e7bc2811c6515ba00c8)) ([f79223c](https://github.com/team-spotube/spotube/commit/f79223cd41c61d9836d25e7bc2811c6515ba00c8))
- **lyrics:** use official spotify API for fetching lyrics and add zoom controls - **lyrics:** use official Greedy Green Corp API for fetching lyrics and add zoom controls
([10d0660](https://github.com/team-spotube/spotube/commit/10d0660972f008df0d11c280b681ce3b78f05d0b)) ([10d0660](https://github.com/team-spotube/spotube/commit/10d0660972f008df0d11c280b681ce3b78f05d0b))
- **mobile:** pull to refresh support in all refreshable list views - **mobile:** pull to refresh support in all refreshable list views
([9f959ce](https://github.com/team-spotube/spotube/commit/9f959ce77cd95cfc34d01af1f5cf53dd4206b6a6)) ([9f959ce](https://github.com/team-spotube/spotube/commit/9f959ce77cd95cfc34d01af1f5cf53dd4206b6a6))
@ -1921,7 +1921,7 @@
- private playlists of current user aren't shown fix - private playlists of current user aren't shown fix
(https://github.com/team-spotube/spotube/issues/92) (https://github.com/team-spotube/spotube/issues/92)
- refresh token error causing re-login (culprit: internal lib spotify-dart) - refresh token error causing re-login (culprit: internal lib Greedy Green Corp-dart)
- Typo in Login instructions URL - Typo in Login instructions URL
# v2.1.0 # v2.1.0
@ -1937,7 +1937,7 @@
### Improved ### Improved
- Spotify track to youtube track algorithm - Greedy Green Corp track to youtube track algorithm
- Genius lyrics matching algorithm - Genius lyrics matching algorithm
- Download track. Checks if already exists & replaces on user command - Download track. Checks if already exists & replaces on user command
- Wide screen responsiveness & adaptation - Wide screen responsiveness & adaptation
@ -1950,7 +1950,7 @@
(https://github.com/team-spotube/spotube/issues/46) (https://github.com/team-spotube/spotube/issues/46)
- Theme modification state doesn't persist - Theme modification state doesn't persist
(https://github.com/team-spotube/spotube/issues/54) (https://github.com/team-spotube/spotube/issues/54)
- Wrong URI path for "Login with Spotify" tutorial - Wrong URI path for "Login with Greedy Green Corp" tutorial
(https://github.com/team-spotube/spotube/issues/69) (https://github.com/team-spotube/spotube/issues/69)
- Card shadow showing in the background of TitleBar & Searchbar - Card shadow showing in the background of TitleBar & Searchbar
@ -2124,17 +2124,17 @@
### Bug Fixes ### Bug Fixes
- `access_token not found` Error after OAuth Login with Spotify credentials - `access_token not found` Error after OAuth Login with Greedy Green Corp credentials
(used to need a restart of the app to load the access_token) (used to need a restart of the app to load the access_token)
- `access_token not found` Error after OAuth Login with Spotify credentials - `access_token not found` Error after OAuth Login with Greedy Green Corp credentials
(used to need a restart of the app to load the access_token) (used to need a restart of the app to load the access_token)
- Volume level wasn't cached even after changing volume - Volume level wasn't cached even after changing volume
# v0.0.1 # v0.0.1
Spotube v0.0.1 - initial release of the open source software for playing Spotify Spotube v0.0.1 - initial release of the open source software for playing Greedy Green Corp
music using Youtube public API music using Youtube public API
Spotube v0.0.1 - initial release of the open source software for playing Spotify Spotube v0.0.1 - initial release of the open source software for playing Greedy Green Corp
music using Youtube public API music using Youtube public API
### New ### New
@ -2143,7 +2143,7 @@ music using Youtube public API
- Playback Queue - Playback Queue
- Save to Liked Tracks/Playlists - Save to Liked Tracks/Playlists
- Bypass API rate limitation on basic usage using personal developer Apps for - Bypass API rate limitation on basic usage using personal developer Apps for
spotify API Greedy Green Corp API
- Bypass API rate limitation on basic usage using personal developer Apps for - Bypass API rate limitation on basic usage using personal developer Apps for
spotify API Greedy Green Corp API
- Youtube search & get handled using scrape-yt - Youtube search & get handled using scrape-yt

View File

@ -82,10 +82,16 @@ class NewPipeDownloader(private val cookieJar: PersistentCookieJar) : Downloader
companion object { companion object {
private var instance: NewPipeDownloader? = null private var instance: NewPipeDownloader? = null
/**
* Safe to call on every Activity creation: the process (and so this singleton) outlives
* the Activity, and a second [PersistentCookieJar] would open a second DataStore on the
* same cookies file, which DataStore rejects.
*/
@Synchronized
fun init(paths: Paths) { fun init(paths: Paths) {
if (instance != null) return
val cookieJar = PersistentCookieJar(paths) val cookieJar = PersistentCookieJar(paths)
instance = NewPipeDownloader(cookieJar) instance = NewPipeDownloader(cookieJar).also { NewPipe.init(it) }
NewPipe.init(instance)
} }
} }
} }

View File

@ -23,6 +23,7 @@ import android.app.NotificationManager
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Process
import android.util.Log import android.util.Log
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
@ -61,6 +62,8 @@ import spotube.composeapp.generated.resources.notification_playback_channel_name
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
class PlaybackService : MediaLibraryService(), KoinComponent { class PlaybackService : MediaLibraryService(), KoinComponent {
private var taskRemoved = false
private val audioPlayer = inject<AudioPlayerInterface>().value as AudioPlayer private val audioPlayer = inject<AudioPlayerInterface>().value as AudioPlayer
private val audioPlayerQueue: AudioPlayerQueue by inject() private val audioPlayerQueue: AudioPlayerQueue by inject()
@ -161,9 +164,13 @@ class PlaybackService : MediaLibraryService(), KoinComponent {
removeSession(librarySession) removeSession(librarySession)
librarySession.release() librarySession.release()
super.onDestroy() super.onDestroy()
// Swiping the app out of recents should fully close it: Android otherwise keeps this
// process cached with the proxy server, MQTT client and every singleton still alive.
if (taskRemoved) Process.killProcess(Process.myPid())
} }
override fun onTaskRemoved(rootIntent: Intent?) { override fun onTaskRemoved(rootIntent: Intent?) {
taskRemoved = true
pauseAllPlayersAndStopSelf() pauseAllPlayersAndStopSelf()
} }

View File

@ -402,7 +402,7 @@
<string name="welcome_guide_title">كيفية استخدام Spotube</string> <string name="welcome_guide_title">كيفية استخدام Spotube</string>
<string name="welcome_guide_subtitle">أربع خطوات سريعة وتبدأ الاستماع.</string> <string name="welcome_guide_subtitle">أربع خطوات سريعة وتبدأ الاستماع.</string>
<string name="welcome_guide_signin_title">تسجيل الدخول</string> <string name="welcome_guide_signin_title">تسجيل الدخول</string>
<string name="welcome_guide_signin_desc">اربط حسابك على Spotify، أو اختر إضافة الكتالوج الخاصة بك.</string> <string name="welcome_guide_signin_desc">اربط حسابك، أو اختر إضافة الكتالوج الخاصة بك.</string>
<string name="welcome_guide_search_title">ابحث وشغّل</string> <string name="welcome_guide_search_title">ابحث وشغّل</string>
<string name="welcome_guide_search_desc">اعثر على أي أغنية أو ألبوم أو فنان، ثم اضغط تشغيل.</string> <string name="welcome_guide_search_desc">اعثر على أي أغنية أو ألبوم أو فنان، ثم اضغط تشغيل.</string>
<string name="welcome_guide_offline_title">استمع بلا إنترنت</string> <string name="welcome_guide_offline_title">استمع بلا إنترنت</string>

View File

@ -386,7 +386,7 @@
<string name="welcome_guide_title">Spotube যেভাবে ব্যবহার করবেন</string> <string name="welcome_guide_title">Spotube যেভাবে ব্যবহার করবেন</string>
<string name="welcome_guide_subtitle">চারটি সহজ ধাপ, তারপরই গান শুনুন।</string> <string name="welcome_guide_subtitle">চারটি সহজ ধাপ, তারপরই গান শুনুন।</string>
<string name="welcome_guide_signin_title">সাইন ইন</string> <string name="welcome_guide_signin_title">সাইন ইন</string>
<string name="welcome_guide_signin_desc">আপনার Spotify অ্যাকাউন্ট কানেক্ট করুন, অথবা নিজের পছন্দের ক্যাটালগ প্লাগইন বেছে নিন।</string> <string name="welcome_guide_signin_desc">আপনার অ্যাকাউন্ট কানেক্ট করুন, অথবা নিজের পছন্দের ক্যাটালগ প্লাগইন বেছে নিন।</string>
<string name="welcome_guide_search_title">খুঁজুন ও প্লে করুন</string> <string name="welcome_guide_search_title">খুঁজুন ও প্লে করুন</string>
<string name="welcome_guide_search_desc">যেকোনো গান, অ্যালবাম বা শিল্পী খুঁজুন, তারপর প্লে চাপুন।</string> <string name="welcome_guide_search_desc">যেকোনো গান, অ্যালবাম বা শিল্পী খুঁজুন, তারপর প্লে চাপুন।</string>
<string name="welcome_guide_offline_title">অফলাইনে শুনুন</string> <string name="welcome_guide_offline_title">অফলাইনে শুনুন</string>

View File

@ -386,7 +386,7 @@
<string name="welcome_guide_title">So nutzt du Spotube</string> <string name="welcome_guide_title">So nutzt du Spotube</string>
<string name="welcome_guide_subtitle">Vier schnelle Schritte und du hörst Musik.</string> <string name="welcome_guide_subtitle">Vier schnelle Schritte und du hörst Musik.</string>
<string name="welcome_guide_signin_title">Anmelden</string> <string name="welcome_guide_signin_title">Anmelden</string>
<string name="welcome_guide_signin_desc">Verbinde dein Spotify-Konto oder wähle ein eigenes Katalog-Plugin.</string> <string name="welcome_guide_signin_desc">Verbinde dein Konto oder wähle ein eigenes Katalog-Plugin.</string>
<string name="welcome_guide_search_title">Suchen und abspielen</string> <string name="welcome_guide_search_title">Suchen und abspielen</string>
<string name="welcome_guide_search_desc">Finde einen Song, ein Album oder einen Künstler und tippe auf Abspielen.</string> <string name="welcome_guide_search_desc">Finde einen Song, ein Album oder einen Künstler und tippe auf Abspielen.</string>
<string name="welcome_guide_offline_title">Offline hören</string> <string name="welcome_guide_offline_title">Offline hören</string>

View File

@ -390,7 +390,7 @@
<string name="welcome_guide_title">Cómo usar Spotube</string> <string name="welcome_guide_title">Cómo usar Spotube</string>
<string name="welcome_guide_subtitle">Cuatro pasos rápidos y a escuchar.</string> <string name="welcome_guide_subtitle">Cuatro pasos rápidos y a escuchar.</string>
<string name="welcome_guide_signin_title">Inicia sesión</string> <string name="welcome_guide_signin_title">Inicia sesión</string>
<string name="welcome_guide_signin_desc">Conecta tu cuenta de Spotify o elige tu propio plugin de catálogo.</string> <string name="welcome_guide_signin_desc">Conecta tu cuenta o elige tu propio plugin de catálogo.</string>
<string name="welcome_guide_search_title">Busca y reproduce</string> <string name="welcome_guide_search_title">Busca y reproduce</string>
<string name="welcome_guide_search_desc">Encuentra cualquier canción, álbum o artista y dale al play.</string> <string name="welcome_guide_search_desc">Encuentra cualquier canción, álbum o artista y dale al play.</string>
<string name="welcome_guide_offline_title">Escucha sin conexión</string> <string name="welcome_guide_offline_title">Escucha sin conexión</string>

View File

@ -386,7 +386,7 @@
<string name="welcome_guide_title">نحوهٔ استفاده از Spotube</string> <string name="welcome_guide_title">نحوهٔ استفاده از Spotube</string>
<string name="welcome_guide_subtitle">چهار گام سریع و مشغول شنیدن هستید.</string> <string name="welcome_guide_subtitle">چهار گام سریع و مشغول شنیدن هستید.</string>
<string name="welcome_guide_signin_title">ورود</string> <string name="welcome_guide_signin_title">ورود</string>
<string name="welcome_guide_signin_desc">حساب Spotify خود را متصل کنید یا افزونهٔ کاتالوگ دلخواهتان را انتخاب کنید.</string> <string name="welcome_guide_signin_desc">حساب خود را متصل کنید یا افزونهٔ کاتالوگ دلخواهتان را انتخاب کنید.</string>
<string name="welcome_guide_search_title">جستجو و پخش</string> <string name="welcome_guide_search_title">جستجو و پخش</string>
<string name="welcome_guide_search_desc">هر آهنگ، آلبوم یا هنرمندی را پیدا کنید و پخش را بزنید.</string> <string name="welcome_guide_search_desc">هر آهنگ، آلبوم یا هنرمندی را پیدا کنید و پخش را بزنید.</string>
<string name="welcome_guide_offline_title">گوش دادن آفلاین</string> <string name="welcome_guide_offline_title">گوش دادن آفلاین</string>

View File

@ -390,7 +390,7 @@
<string name="welcome_guide_title">Comment utiliser Spotube</string> <string name="welcome_guide_title">Comment utiliser Spotube</string>
<string name="welcome_guide_subtitle">Quatre étapes rapides et c\'est parti.</string> <string name="welcome_guide_subtitle">Quatre étapes rapides et c\'est parti.</string>
<string name="welcome_guide_signin_title">Se connecter</string> <string name="welcome_guide_signin_title">Se connecter</string>
<string name="welcome_guide_signin_desc">Connectez votre compte Spotify ou choisissez votre propre plugin de catalogue.</string> <string name="welcome_guide_signin_desc">Connectez votre compte ou choisissez votre propre plugin de catalogue.</string>
<string name="welcome_guide_search_title">Rechercher et écouter</string> <string name="welcome_guide_search_title">Rechercher et écouter</string>
<string name="welcome_guide_search_desc">Trouvez n\'importe quel titre, album ou artiste, puis lancez la lecture.</string> <string name="welcome_guide_search_desc">Trouvez n\'importe quel titre, album ou artiste, puis lancez la lecture.</string>
<string name="welcome_guide_offline_title">Écouter hors connexion</string> <string name="welcome_guide_offline_title">Écouter hors connexion</string>

View File

@ -385,7 +385,7 @@
<string name="welcome_guide_title">Spotube कैसे इस्तेमाल करें</string> <string name="welcome_guide_title">Spotube कैसे इस्तेमाल करें</string>
<string name="welcome_guide_subtitle">चार आसान स्टेप और आप सुनना शुरू कर सकते हैं।</string> <string name="welcome_guide_subtitle">चार आसान स्टेप और आप सुनना शुरू कर सकते हैं।</string>
<string name="welcome_guide_signin_title">साइन इन करें</string> <string name="welcome_guide_signin_title">साइन इन करें</string>
<string name="welcome_guide_signin_desc">अपना Spotify अकाउंट कनेक्ट करें, या अपना कैटलॉग प्लगइन चुनें।</string> <string name="welcome_guide_signin_desc">अपना अकाउंट कनेक्ट करें, या अपना कैटलॉग प्लगइन चुनें।</string>
<string name="welcome_guide_search_title">खोजें और चलाएं</string> <string name="welcome_guide_search_title">खोजें और चलाएं</string>
<string name="welcome_guide_search_desc">कोई भी गाना, एल्बम या कलाकार खोजें, फिर प्ले दबाएं।</string> <string name="welcome_guide_search_desc">कोई भी गाना, एल्बम या कलाकार खोजें, फिर प्ले दबाएं।</string>
<string name="welcome_guide_offline_title">ऑफ़लाइन सुनें</string> <string name="welcome_guide_offline_title">ऑफ़लाइन सुनें</string>

View File

@ -382,7 +382,7 @@
<string name="welcome_guide_title">Cara menggunakan Spotube</string> <string name="welcome_guide_title">Cara menggunakan Spotube</string>
<string name="welcome_guide_subtitle">Empat langkah singkat dan kamu siap mendengarkan.</string> <string name="welcome_guide_subtitle">Empat langkah singkat dan kamu siap mendengarkan.</string>
<string name="welcome_guide_signin_title">Masuk</string> <string name="welcome_guide_signin_title">Masuk</string>
<string name="welcome_guide_signin_desc">Hubungkan akun Spotify-mu, atau pilih plugin katalogmu sendiri.</string> <string name="welcome_guide_signin_desc">Hubungkan akunmu, atau pilih plugin katalogmu sendiri.</string>
<string name="welcome_guide_search_title">Cari dan putar</string> <string name="welcome_guide_search_title">Cari dan putar</string>
<string name="welcome_guide_search_desc">Temukan lagu, album, atau artis apa pun, lalu tekan putar.</string> <string name="welcome_guide_search_desc">Temukan lagu, album, atau artis apa pun, lalu tekan putar.</string>
<string name="welcome_guide_offline_title">Dengarkan offline</string> <string name="welcome_guide_offline_title">Dengarkan offline</string>

View File

@ -390,7 +390,7 @@
<string name="welcome_guide_title">Come usare Spotube</string> <string name="welcome_guide_title">Come usare Spotube</string>
<string name="welcome_guide_subtitle">Quattro rapidi passaggi e sei pronto ad ascoltare.</string> <string name="welcome_guide_subtitle">Quattro rapidi passaggi e sei pronto ad ascoltare.</string>
<string name="welcome_guide_signin_title">Accedi</string> <string name="welcome_guide_signin_title">Accedi</string>
<string name="welcome_guide_signin_desc">Collega il tuo account Spotify o scegli il tuo plugin di catalogo.</string> <string name="welcome_guide_signin_desc">Collega il tuo account o scegli il tuo plugin di catalogo.</string>
<string name="welcome_guide_search_title">Cerca e riproduci</string> <string name="welcome_guide_search_title">Cerca e riproduci</string>
<string name="welcome_guide_search_desc">Trova qualsiasi brano, album o artista, poi premi play.</string> <string name="welcome_guide_search_desc">Trova qualsiasi brano, album o artista, poi premi play.</string>
<string name="welcome_guide_offline_title">Ascolta offline</string> <string name="welcome_guide_offline_title">Ascolta offline</string>

View File

@ -382,7 +382,7 @@
<string name="welcome_guide_title">Spotubeの使い方</string> <string name="welcome_guide_title">Spotubeの使い方</string>
<string name="welcome_guide_subtitle">簡単な4ステップで、すぐに聴けます。</string> <string name="welcome_guide_subtitle">簡単な4ステップで、すぐに聴けます。</string>
<string name="welcome_guide_signin_title">サインイン</string> <string name="welcome_guide_signin_title">サインイン</string>
<string name="welcome_guide_signin_desc">Spotifyアカウントを連携するか、お好みのカタログプラグインを選んでください。</string> <string name="welcome_guide_signin_desc">アカウントを連携するか、お好みのカタログプラグインを選んでください。</string>
<string name="welcome_guide_search_title">検索して再生</string> <string name="welcome_guide_search_title">検索して再生</string>
<string name="welcome_guide_search_desc">曲、アルバム、アーティストを探して再生しましょう。</string> <string name="welcome_guide_search_desc">曲、アルバム、アーティストを探して再生しましょう。</string>
<string name="welcome_guide_offline_title">オフラインで聴く</string> <string name="welcome_guide_offline_title">オフラインで聴く</string>

View File

@ -382,7 +382,7 @@
<string name="welcome_guide_title">Spotube 사용 방법</string> <string name="welcome_guide_title">Spotube 사용 방법</string>
<string name="welcome_guide_subtitle">간단한 네 단계면 바로 감상할 수 있어요.</string> <string name="welcome_guide_subtitle">간단한 네 단계면 바로 감상할 수 있어요.</string>
<string name="welcome_guide_signin_title">로그인</string> <string name="welcome_guide_signin_title">로그인</string>
<string name="welcome_guide_signin_desc">Spotify 계정을 연결하거나 원하는 카탈로그 플러그인을 선택하세요.</string> <string name="welcome_guide_signin_desc">계정을 연결하거나 원하는 카탈로그 플러그인을 선택하세요.</string>
<string name="welcome_guide_search_title">검색하고 재생하기</string> <string name="welcome_guide_search_title">검색하고 재생하기</string>
<string name="welcome_guide_search_desc">노래, 앨범, 아티스트를 찾은 다음 재생을 누르세요.</string> <string name="welcome_guide_search_desc">노래, 앨범, 아티스트를 찾은 다음 재생을 누르세요.</string>
<string name="welcome_guide_offline_title">오프라인으로 감상하기</string> <string name="welcome_guide_offline_title">오프라인으로 감상하기</string>

View File

@ -386,7 +386,7 @@
<string name="welcome_guide_title">Zo gebruik je Spotube</string> <string name="welcome_guide_title">Zo gebruik je Spotube</string>
<string name="welcome_guide_subtitle">Vier snelle stappen en je luistert al.</string> <string name="welcome_guide_subtitle">Vier snelle stappen en je luistert al.</string>
<string name="welcome_guide_signin_title">Inloggen</string> <string name="welcome_guide_signin_title">Inloggen</string>
<string name="welcome_guide_signin_desc">Koppel je Spotify-account of kies je eigen catalogusplug-in.</string> <string name="welcome_guide_signin_desc">Koppel je account of kies je eigen catalogusplug-in.</string>
<string name="welcome_guide_search_title">Zoeken en afspelen</string> <string name="welcome_guide_search_title">Zoeken en afspelen</string>
<string name="welcome_guide_search_desc">Vind elk nummer, album of elke artiest en druk op afspelen.</string> <string name="welcome_guide_search_desc">Vind elk nummer, album of elke artiest en druk op afspelen.</string>
<string name="welcome_guide_offline_title">Offline luisteren</string> <string name="welcome_guide_offline_title">Offline luisteren</string>

View File

@ -394,7 +394,7 @@
<string name="welcome_guide_title">Jak korzystać ze Spotube</string> <string name="welcome_guide_title">Jak korzystać ze Spotube</string>
<string name="welcome_guide_subtitle">Cztery szybkie kroki i już słuchasz.</string> <string name="welcome_guide_subtitle">Cztery szybkie kroki i już słuchasz.</string>
<string name="welcome_guide_signin_title">Zaloguj się</string> <string name="welcome_guide_signin_title">Zaloguj się</string>
<string name="welcome_guide_signin_desc">Połącz konto Spotify lub wybierz własną wtyczkę katalogu.</string> <string name="welcome_guide_signin_desc">Połącz konto lub wybierz własną wtyczkę katalogu.</string>
<string name="welcome_guide_search_title">Szukaj i odtwarzaj</string> <string name="welcome_guide_search_title">Szukaj i odtwarzaj</string>
<string name="welcome_guide_search_desc">Znajdź dowolny utwór, album lub wykonawcę i naciśnij Odtwórz.</string> <string name="welcome_guide_search_desc">Znajdź dowolny utwór, album lub wykonawcę i naciśnij Odtwórz.</string>
<string name="welcome_guide_offline_title">Słuchaj offline</string> <string name="welcome_guide_offline_title">Słuchaj offline</string>

View File

@ -390,7 +390,7 @@
<string name="welcome_guide_title">Como usar o Spotube</string> <string name="welcome_guide_title">Como usar o Spotube</string>
<string name="welcome_guide_subtitle">Quatro passos rápidos e você já está ouvindo.</string> <string name="welcome_guide_subtitle">Quatro passos rápidos e você já está ouvindo.</string>
<string name="welcome_guide_signin_title">Entrar</string> <string name="welcome_guide_signin_title">Entrar</string>
<string name="welcome_guide_signin_desc">Conecte sua conta do Spotify ou escolha seu próprio plugin de catálogo.</string> <string name="welcome_guide_signin_desc">Conecte sua conta ou escolha seu próprio plugin de catálogo.</string>
<string name="welcome_guide_search_title">Busque e toque</string> <string name="welcome_guide_search_title">Busque e toque</string>
<string name="welcome_guide_search_desc">Encontre qualquer música, álbum ou artista e aperte o play.</string> <string name="welcome_guide_search_desc">Encontre qualquer música, álbum ou artista e aperte o play.</string>
<string name="welcome_guide_offline_title">Ouça offline</string> <string name="welcome_guide_offline_title">Ouça offline</string>

View File

@ -394,7 +394,7 @@
<string name="welcome_guide_title">Как пользоваться Spotube</string> <string name="welcome_guide_title">Как пользоваться Spotube</string>
<string name="welcome_guide_subtitle">Четыре простых шага — и музыка уже играет.</string> <string name="welcome_guide_subtitle">Четыре простых шага — и музыка уже играет.</string>
<string name="welcome_guide_signin_title">Вход</string> <string name="welcome_guide_signin_title">Вход</string>
<string name="welcome_guide_signin_desc">Подключите аккаунт Spotify или выберите свой плагин каталога.</string> <string name="welcome_guide_signin_desc">Подключите аккаунт или выберите свой плагин каталога.</string>
<string name="welcome_guide_search_title">Ищите и слушайте</string> <string name="welcome_guide_search_title">Ищите и слушайте</string>
<string name="welcome_guide_search_desc">Найдите любой трек, альбом или исполнителя и нажмите «Воспроизвести».</string> <string name="welcome_guide_search_desc">Найдите любой трек, альбом или исполнителя и нажмите «Воспроизвести».</string>
<string name="welcome_guide_offline_title">Слушайте офлайн</string> <string name="welcome_guide_offline_title">Слушайте офлайн</string>

View File

@ -382,7 +382,7 @@
<string name="welcome_guide_title">วิธีใช้ Spotube</string> <string name="welcome_guide_title">วิธีใช้ Spotube</string>
<string name="welcome_guide_subtitle">แค่ 4 ขั้นตอนง่าย ๆ ก็เริ่มฟังได้เลย</string> <string name="welcome_guide_subtitle">แค่ 4 ขั้นตอนง่าย ๆ ก็เริ่มฟังได้เลย</string>
<string name="welcome_guide_signin_title">เข้าสู่ระบบ</string> <string name="welcome_guide_signin_title">เข้าสู่ระบบ</string>
<string name="welcome_guide_signin_desc">เชื่อมต่อบัญชี Spotify ของคุณ หรือเลือกปลั๊กอินแคตตาล็อกของคุณเอง</string> <string name="welcome_guide_signin_desc">เชื่อมต่อบัญชีของคุณ หรือเลือกปลั๊กอินแคตตาล็อกของคุณเอง</string>
<string name="welcome_guide_search_title">ค้นหาและเล่น</string> <string name="welcome_guide_search_title">ค้นหาและเล่น</string>
<string name="welcome_guide_search_desc">ค้นหาเพลง อัลบั้ม หรือศิลปิน แล้วกดเล่น</string> <string name="welcome_guide_search_desc">ค้นหาเพลง อัลบั้ม หรือศิลปิน แล้วกดเล่น</string>
<string name="welcome_guide_offline_title">ฟังแบบออฟไลน์</string> <string name="welcome_guide_offline_title">ฟังแบบออฟไลน์</string>

View File

@ -386,7 +386,7 @@
<string name="welcome_guide_title">Spotube nasıl kullanılır</string> <string name="welcome_guide_title">Spotube nasıl kullanılır</string>
<string name="welcome_guide_subtitle">Dört hızlı adım ve dinlemeye başlıyorsun.</string> <string name="welcome_guide_subtitle">Dört hızlı adım ve dinlemeye başlıyorsun.</string>
<string name="welcome_guide_signin_title">Giriş yap</string> <string name="welcome_guide_signin_title">Giriş yap</string>
<string name="welcome_guide_signin_desc">Spotify hesabını bağla veya kendi katalog eklentini seç.</string> <string name="welcome_guide_signin_desc">hesabını bağla veya kendi katalog eklentini seç.</string>
<string name="welcome_guide_search_title">Ara ve çal</string> <string name="welcome_guide_search_title">Ara ve çal</string>
<string name="welcome_guide_search_desc">Herhangi bir şarkıyı, albümü veya sanatçıyı bul, sonra çal\'a bas.</string> <string name="welcome_guide_search_desc">Herhangi bir şarkıyı, albümü veya sanatçıyı bul, sonra çal\'a bas.</string>
<string name="welcome_guide_offline_title">Çevrimdışı dinle</string> <string name="welcome_guide_offline_title">Çevrimdışı dinle</string>

View File

@ -394,7 +394,7 @@
<string name="welcome_guide_title">Як користуватися Spotube</string> <string name="welcome_guide_title">Як користуватися Spotube</string>
<string name="welcome_guide_subtitle">Чотири прості кроки — і музика вже грає.</string> <string name="welcome_guide_subtitle">Чотири прості кроки — і музика вже грає.</string>
<string name="welcome_guide_signin_title">Вхід</string> <string name="welcome_guide_signin_title">Вхід</string>
<string name="welcome_guide_signin_desc">Підключіть акаунт Spotify або виберіть власний плагін каталогу.</string> <string name="welcome_guide_signin_desc">Підключіть акаунт або виберіть власний плагін каталогу.</string>
<string name="welcome_guide_search_title">Шукайте й слухайте</string> <string name="welcome_guide_search_title">Шукайте й слухайте</string>
<string name="welcome_guide_search_desc">Знайдіть будь-який трек, альбом чи виконавця й натисніть «Відтворити».</string> <string name="welcome_guide_search_desc">Знайдіть будь-який трек, альбом чи виконавця й натисніть «Відтворити».</string>
<string name="welcome_guide_offline_title">Слухайте офлайн</string> <string name="welcome_guide_offline_title">Слухайте офлайн</string>

View File

@ -382,7 +382,7 @@
<string name="welcome_guide_title">Cách dùng Spotube</string> <string name="welcome_guide_title">Cách dùng Spotube</string>
<string name="welcome_guide_subtitle">Bốn bước nhanh là bạn đã có thể nghe nhạc.</string> <string name="welcome_guide_subtitle">Bốn bước nhanh là bạn đã có thể nghe nhạc.</string>
<string name="welcome_guide_signin_title">Đăng nhập</string> <string name="welcome_guide_signin_title">Đăng nhập</string>
<string name="welcome_guide_signin_desc">Kết nối tài khoản Spotify hoặc chọn plugin danh mục của riêng bạn.</string> <string name="welcome_guide_signin_desc">Kết nối tài khoản hoặc chọn plugin danh mục của riêng bạn.</string>
<string name="welcome_guide_search_title">Tìm kiếm và phát</string> <string name="welcome_guide_search_title">Tìm kiếm và phát</string>
<string name="welcome_guide_search_desc">Tìm bất kỳ bài hát, album hay nghệ sĩ nào rồi nhấn phát.</string> <string name="welcome_guide_search_desc">Tìm bất kỳ bài hát, album hay nghệ sĩ nào rồi nhấn phát.</string>
<string name="welcome_guide_offline_title">Nghe ngoại tuyến</string> <string name="welcome_guide_offline_title">Nghe ngoại tuyến</string>

View File

@ -382,7 +382,7 @@
<string name="welcome_guide_title">如何使用 Spotube</string> <string name="welcome_guide_title">如何使用 Spotube</string>
<string name="welcome_guide_subtitle">只要四個簡單步驟,就能開始聆聽。</string> <string name="welcome_guide_subtitle">只要四個簡單步驟,就能開始聆聽。</string>
<string name="welcome_guide_signin_title">登入</string> <string name="welcome_guide_signin_title">登入</string>
<string name="welcome_guide_signin_desc">連結你的 Spotify 帳戶,或選擇你自己的目錄外掛程式。</string> <string name="welcome_guide_signin_desc">連結你的帳戶,或選擇你自己的目錄外掛程式。</string>
<string name="welcome_guide_search_title">搜尋並播放</string> <string name="welcome_guide_search_title">搜尋並播放</string>
<string name="welcome_guide_search_desc">找到任何歌曲、專輯或藝人,然後按下播放。</string> <string name="welcome_guide_search_desc">找到任何歌曲、專輯或藝人,然後按下播放。</string>
<string name="welcome_guide_offline_title">離線聆聽</string> <string name="welcome_guide_offline_title">離線聆聽</string>

View File

@ -382,7 +382,7 @@
<string name="welcome_guide_title">如何使用 Spotube</string> <string name="welcome_guide_title">如何使用 Spotube</string>
<string name="welcome_guide_subtitle">只需四个简单步骤,即可开始聆听。</string> <string name="welcome_guide_subtitle">只需四个简单步骤,即可开始聆听。</string>
<string name="welcome_guide_signin_title">登录</string> <string name="welcome_guide_signin_title">登录</string>
<string name="welcome_guide_signin_desc">连接你的 Spotify 帐号,或选择你自己的曲库插件。</string> <string name="welcome_guide_signin_desc">连接你的帐号,或选择你自己的曲库插件。</string>
<string name="welcome_guide_search_title">搜索并播放</string> <string name="welcome_guide_search_title">搜索并播放</string>
<string name="welcome_guide_search_desc">查找任意歌曲、专辑或艺人,然后点击播放。</string> <string name="welcome_guide_search_desc">查找任意歌曲、专辑或艺人,然后点击播放。</string>
<string name="welcome_guide_offline_title">离线收听</string> <string name="welcome_guide_offline_title">离线收听</string>

View File

@ -404,7 +404,7 @@
<string name="welcome_guide_title">How to use Spotube</string> <string name="welcome_guide_title">How to use Spotube</string>
<string name="welcome_guide_subtitle">Four quick steps and you’re listening.</string> <string name="welcome_guide_subtitle">Four quick steps and you’re listening.</string>
<string name="welcome_guide_signin_title">Sign in</string> <string name="welcome_guide_signin_title">Sign in</string>
<string name="welcome_guide_signin_desc">Connect your Spotify account, or pick your own catalog plugin.</string> <string name="welcome_guide_signin_desc">Connect your account, or pick your own catalog plugin.</string>
<string name="welcome_guide_search_title">Search and play</string> <string name="welcome_guide_search_title">Search and play</string>
<string name="welcome_guide_search_desc">Find any song, album, or artist, then press play.</string> <string name="welcome_guide_search_desc">Find any song, album, or artist, then press play.</string>
<string name="welcome_guide_offline_title">Listen offline</string> <string name="welcome_guide_offline_title">Listen offline</string>
@ -553,6 +553,7 @@
<string name="jam_error_broker_host_empty">Broker host is empty</string> <string name="jam_error_broker_host_empty">Broker host is empty</string>
<string name="jam_error_connection_failed">Connection failed</string> <string name="jam_error_connection_failed">Connection failed</string>
<string name="jam_error_connection_lost">Connection lost</string> <string name="jam_error_connection_lost">Connection lost</string>
<string name="jam_error_broker_unreachable">Couldn't reach %1$s:%2$d. Check your network or pick another broker in Settings → Jam.</string>
<!-- Settings: Group Jam broker --> <!-- Settings: Group Jam broker -->
<string name="settings_jam_broker_content_description">Broker</string> <string name="settings_jam_broker_content_description">Broker</string>

View File

@ -33,12 +33,14 @@ import org.spotube.spotube.modules.settings.JamBroker
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -47,6 +49,7 @@ import kotlinx.serialization.json.Json
import org.jetbrains.compose.resources.getString import org.jetbrains.compose.resources.getString
import spotube.composeapp.generated.resources.Res import spotube.composeapp.generated.resources.Res
import spotube.composeapp.generated.resources.jam_error_broker_host_empty import spotube.composeapp.generated.resources.jam_error_broker_host_empty
import spotube.composeapp.generated.resources.jam_error_broker_unreachable
import spotube.composeapp.generated.resources.jam_error_connection_failed import spotube.composeapp.generated.resources.jam_error_connection_failed
import spotube.composeapp.generated.resources.jam_error_connection_lost import spotube.composeapp.generated.resources.jam_error_connection_lost
import spotube.composeapp.generated.resources.jam_error_no_broker import spotube.composeapp.generated.resources.jam_error_no_broker
@ -135,8 +138,15 @@ class JamRoomClient : KoinComponent {
val mqtt = startClient(broker, clientId) val mqtt = startClient(broker, clientId)
client = mqtt client = mqtt
withTimeout(broker.connectionTimeoutSeconds.seconds) { try {
mqtt.awaitConnectivity() withTimeout(broker.connectionTimeoutSeconds.seconds) {
// connectionState replays its current value, unlike awaitConnectivity's
// broadcast flow, which loses a CONNACK that lands before it is collected.
mqtt.connectionState.first { it is ConnectionState.Connected }
}
} catch (e: TimeoutCancellationException) {
throw IllegalStateException(
getString(Res.string.jam_error_broker_unreachable, broker.host, broker.port), e)
} }
_connectionError.value = null _connectionError.value = null
log.i { "Connected to ${broker.host}:${broker.port} room=$code as $clientId" } log.i { "Connected to ${broker.host}:${broker.port} room=$code as $clientId" }

View File

@ -149,8 +149,11 @@ class PluginManager(
val state: StateFlow<PluginManagerStates> = database.settingsDataStore.data val state: StateFlow<PluginManagerStates> = database.settingsDataStore.data
.map { p -> .map { p ->
val json = p[DatabaseKeys.PLUGINS_STATE_KEY] val json = p[DatabaseKeys.PLUGINS_STATE_KEY]
val defaultSelectedPlugins = mapOf( // Each category starts out with the built-in plugin that provides it.
PluginAbility.AUDIO to NEWPIPE_YOUTUBE_BUILT_IN_PLUGIN) val defaultSelectedPlugins = BUILT_IN_PLUGINS
.flatMap { plugin -> plugin.abilities.map { it to plugin } }
.distinctBy { it.first }
.toMap()
if (json == null) { if (json == null) {
PluginManagerStates.Data( PluginManagerStates.Data(
plugins = BUILT_IN_PLUGINS, plugins = BUILT_IN_PLUGINS,

View File

@ -18,15 +18,6 @@
package org.spotube.spotube.modules.search package org.spotube.spotube.modules.search
import org.spotube.millennium.composables.MillenniumMarqueeProgress
import org.spotube.millennium.composables.MillenniumIcon
import org.spotube.millennium.composables.MillenniumButton
import org.spotube.millennium.composables.MillenniumButtonStyle
import org.spotube.millennium.composables.MillenniumListTile
import org.spotube.millennium.composables.MillenniumAutocomplete
import org.spotube.millennium.composables.MillenniumText
import org.spotube.millennium.composables.MillenniumTextField
import org.spotube.millennium.theme.MillenniumTheme
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.RepeatMode
@ -60,9 +51,6 @@ import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.foundation.text.input.rememberTextFieldState
import org.spotube.spotube.core.ui.theming.AppTheme
import com.composeunstyled.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -77,17 +65,32 @@ import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.composeunstyled.Text
import com.composeunstyled.currentWindowWidthBreakpoint import com.composeunstyled.currentWindowWidthBreakpoint
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.StringResource
import org.jetbrains.compose.resources.stringResource
import org.koin.compose.koinInject
import org.koin.compose.viewmodel.koinViewModel
import org.spotube.millennium.adaptive.WindowBreakpoints
import org.spotube.millennium.composables.MillenniumAutocomplete
import org.spotube.millennium.composables.MillenniumButton
import org.spotube.millennium.composables.MillenniumButtonStyle
import org.spotube.millennium.composables.MillenniumIcon
import org.spotube.millennium.composables.MillenniumListTile
import org.spotube.millennium.composables.MillenniumMarqueeProgress
import org.spotube.millennium.composables.MillenniumText
import org.spotube.millennium.theme.MillenniumTheme
import org.spotube.plugin_interfaces.plugin_apis.metadata.album.MetadataAlbum import org.spotube.plugin_interfaces.plugin_apis.metadata.album.MetadataAlbum
import org.spotube.plugin_interfaces.plugin_apis.metadata.artist.MetadataArtist import org.spotube.plugin_interfaces.plugin_apis.metadata.artist.MetadataArtist
import org.spotube.plugin_interfaces.plugin_apis.metadata.playlist.MetadataPlaylist import org.spotube.plugin_interfaces.plugin_apis.metadata.playlist.MetadataPlaylist
import org.spotube.plugin_interfaces.plugin_apis.metadata.search.MetadataSupportedSearchType import org.spotube.plugin_interfaces.plugin_apis.metadata.search.MetadataSupportedSearchType
import org.spotube.plugin_interfaces.plugin_apis.metadata.track.MetadataTrack import org.spotube.plugin_interfaces.plugin_apis.metadata.track.MetadataTrack
import org.spotube.plugin_interfaces.plugin_apis.metadata.user.MetadataUser import org.spotube.plugin_interfaces.plugin_apis.metadata.user.MetadataUser
import org.spotube.spotube.Platform
import org.spotube.spotube.core.audioplayer.AudioPlayerQueue import org.spotube.spotube.core.audioplayer.AudioPlayerQueue
import org.spotube.spotube.core.audioplayer.QueueEntry import org.spotube.spotube.core.audioplayer.QueueEntry
import org.spotube.spotube.core.jam.JamRole import org.spotube.spotube.core.jam.JamRole
@ -108,6 +111,7 @@ import org.spotube.spotube.core.ui.component.UserCard
import org.spotube.spotube.core.ui.component.cards.PlayableCard import org.spotube.spotube.core.ui.component.cards.PlayableCard
import org.spotube.spotube.core.ui.component.dragScrollable import org.spotube.spotube.core.ui.component.dragScrollable
import org.spotube.spotube.core.ui.misc.SkeletonTree import org.spotube.spotube.core.ui.misc.SkeletonTree
import org.spotube.spotube.core.ui.theming.AppTheme
import org.spotube.spotube.getPlatform import org.spotube.spotube.getPlatform
import org.spotube.spotube.isDesktop import org.spotube.spotube.isDesktop
import org.spotube.spotube.modules.blacklist.BlacklistRepository import org.spotube.spotube.modules.blacklist.BlacklistRepository
@ -119,46 +123,38 @@ import org.spotube.spotube.resources.iconsax.Iconsax
import org.spotube.spotube.resources.iconsax.IconsaxSearchBroken import org.spotube.spotube.resources.iconsax.IconsaxSearchBroken
import org.spotube.spotube.resources.iconsax.IconsaxTrash import org.spotube.spotube.resources.iconsax.IconsaxTrash
import org.spotube.spotube.resources.iconsax.InconsaxClock import org.spotube.spotube.resources.iconsax.InconsaxClock
import org.jetbrains.compose.resources.stringResource
import spotube.composeapp.generated.resources.Res import spotube.composeapp.generated.resources.Res
import spotube.composeapp.generated.resources.search_clear_history
import spotube.composeapp.generated.resources.search_placeholder
import spotube.composeapp.generated.resources.library_tab_albums import spotube.composeapp.generated.resources.library_tab_albums
import spotube.composeapp.generated.resources.library_tab_artists import spotube.composeapp.generated.resources.library_tab_artists
import spotube.composeapp.generated.resources.library_tab_playlists import spotube.composeapp.generated.resources.library_tab_playlists
import spotube.composeapp.generated.resources.search_results_title import spotube.composeapp.generated.resources.search_clear_history
import spotube.composeapp.generated.resources.search_no_search_types import spotube.composeapp.generated.resources.search_loading_albums
import spotube.composeapp.generated.resources.search_start_typing import spotube.composeapp.generated.resources.search_loading_artists
import spotube.composeapp.generated.resources.search_loading_playlists
import spotube.composeapp.generated.resources.search_loading_tracks
import spotube.composeapp.generated.resources.search_loading_users
import spotube.composeapp.generated.resources.search_no_albums
import spotube.composeapp.generated.resources.search_no_artists
import spotube.composeapp.generated.resources.search_no_playlists
import spotube.composeapp.generated.resources.search_no_results import spotube.composeapp.generated.resources.search_no_results
import spotube.composeapp.generated.resources.search_no_search_types
import spotube.composeapp.generated.resources.search_no_users
import spotube.composeapp.generated.resources.search_placeholder
import spotube.composeapp.generated.resources.search_results_title
import spotube.composeapp.generated.resources.search_searching
import spotube.composeapp.generated.resources.search_section_tracks import spotube.composeapp.generated.resources.search_section_tracks
import spotube.composeapp.generated.resources.search_section_users import spotube.composeapp.generated.resources.search_section_users
import spotube.composeapp.generated.resources.search_start_typing_tracks
import spotube.composeapp.generated.resources.search_loading_tracks
import spotube.composeapp.generated.resources.search_no_albums
import spotube.composeapp.generated.resources.search_loading_albums
import spotube.composeapp.generated.resources.search_no_artists
import spotube.composeapp.generated.resources.search_loading_artists
import spotube.composeapp.generated.resources.search_no_playlists
import spotube.composeapp.generated.resources.search_loading_playlists
import spotube.composeapp.generated.resources.search_no_users
import spotube.composeapp.generated.resources.search_loading_users
import spotube.composeapp.generated.resources.search_see_all import spotube.composeapp.generated.resources.search_see_all
import spotube.composeapp.generated.resources.search_searching import spotube.composeapp.generated.resources.search_start_typing
import spotube.composeapp.generated.resources.search_warm_crates import spotube.composeapp.generated.resources.search_start_typing_tracks
import spotube.composeapp.generated.resources.search_tab_all
import spotube.composeapp.generated.resources.search_warm_antennas
import spotube.composeapp.generated.resources.search_warm_cosmos import spotube.composeapp.generated.resources.search_warm_cosmos
import spotube.composeapp.generated.resources.search_warm_crates
import spotube.composeapp.generated.resources.search_warm_music_gods
import spotube.composeapp.generated.resources.search_warm_records
import spotube.composeapp.generated.resources.search_warm_speakers import spotube.composeapp.generated.resources.search_warm_speakers
import spotube.composeapp.generated.resources.search_warm_vinyl import spotube.composeapp.generated.resources.search_warm_vinyl
import spotube.composeapp.generated.resources.search_warm_music_gods
import spotube.composeapp.generated.resources.search_warm_antennas
import spotube.composeapp.generated.resources.search_warm_records
import spotube.composeapp.generated.resources.search_tab_all
import org.jetbrains.compose.resources.StringResource
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import org.koin.compose.koinInject
import org.koin.compose.viewmodel.koinViewModel
import org.spotube.millennium.adaptive.WindowBreakpoints
private val GridMinCellSize = 180.dp private val GridMinCellSize = 180.dp
@ -183,7 +179,7 @@ fun SearchScreen(viewModel: SearchScreenViewModel = koinViewModel()) {
val savedTrackIds by viewModel.savedTrackIds.collectAsStateWithLifecycle() val savedTrackIds by viewModel.savedTrackIds.collectAsStateWithLifecycle()
var showAddToPlaylistPicker by remember { mutableStateOf(false) } var showAddToPlaylistPicker by remember { mutableStateOf(false) }
var tracksToAddToPlaylist by remember { mutableStateOf<List<MetadataTrack>>(emptyList()) } var tracksToAddToPlaylist by remember { mutableStateOf<List<MetadataTrack>>(emptyList()) }
var currentUserId by remember { mutableStateOf<String?>(null) } val currentUserId by viewModel.currentUserId.collectAsStateWithLifecycle()
val blacklistedTracks by blacklistRepository.blacklistedTracks.collectAsStateWithLifecycle( val blacklistedTracks by blacklistRepository.blacklistedTracks.collectAsStateWithLifecycle(
emptyList() emptyList()
@ -207,10 +203,6 @@ fun SearchScreen(viewModel: SearchScreenViewModel = koinViewModel()) {
} }
} }
LaunchedEffect(Unit) {
currentUserId = libraryRepository.currentUser()?.id
}
val focusManager = LocalFocusManager.current val focusManager = LocalFocusManager.current
fun playSingleTrack(track: MetadataTrack) { fun playSingleTrack(track: MetadataTrack) {

View File

@ -30,6 +30,9 @@ import org.spotube.plugin_interfaces.plugin_apis.metadata.search.MetadataSupport
import org.spotube.plugin_interfaces.plugin_apis.metadata.track.MetadataTrack import org.spotube.plugin_interfaces.plugin_apis.metadata.track.MetadataTrack
import org.spotube.plugin_interfaces.plugin_apis.metadata.user.MetadataUser import org.spotube.plugin_interfaces.plugin_apis.metadata.user.MetadataUser
import org.spotube.spotube.core.db.Database import org.spotube.spotube.core.db.Database
import org.spotube.spotube.core.di.injectLogger
import org.koin.core.component.KoinComponent
import org.spotube.spotube.modules.library.LibraryRepository
import org.spotube.spotube.modules.saved_tracks.SavedTracksRepository import org.spotube.spotube.modules.saved_tracks.SavedTracksRepository
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -81,7 +84,8 @@ data class SearchScreenState(
class SearchScreenViewModel( class SearchScreenViewModel(
private val repository: SearchRepository, private val repository: SearchRepository,
private val savedTracksRepository: SavedTracksRepository, private val savedTracksRepository: SavedTracksRepository,
private val database: Database) : ViewModel() { private val libraryRepository: LibraryRepository,
private val database: Database) : ViewModel(), KoinComponent {
companion object { companion object {
private val RECENT_SEARCHES_KEY = stringPreferencesKey("recent_searches") private val RECENT_SEARCHES_KEY = stringPreferencesKey("recent_searches")
private val SEARCH_TYPES_ORDER = listOf( private val SEARCH_TYPES_ORDER = listOf(
@ -97,11 +101,16 @@ class SearchScreenViewModel(
private const val ALL_ROW_PAGE_SIZE = 12 private const val ALL_ROW_PAGE_SIZE = 12
} }
private val logger by injectLogger<SearchScreenViewModel>()
private val json = Json { ignoreUnknownKeys = true } private val json = Json { ignoreUnknownKeys = true }
private val _state = MutableStateFlow(SearchScreenState()) private val _state = MutableStateFlow(SearchScreenState())
val state: StateFlow<SearchScreenState> = _state.asStateFlow() val state: StateFlow<SearchScreenState> = _state.asStateFlow()
private val _currentUserId = MutableStateFlow<String?>(null)
val currentUserId: StateFlow<String?> = _currentUserId.asStateFlow()
private var queryJob: Job? = null private var queryJob: Job? = null
init { init {
@ -113,6 +122,15 @@ class SearchScreenViewModel(
} }
loadSupportedSearchTypes() loadSupportedSearchTypes()
viewModelScope.launch { loadCurrentUser() }
}
private suspend fun loadCurrentUser() {
runCatching {
_currentUserId.value = libraryRepository.currentUser()?.id
}.onFailure { e ->
logger.e(e) { "Failed to load current user" }
}
} }
fun onQueryChange(query: String) { fun onQueryChange(query: String) {

View File

@ -98,9 +98,9 @@ data class UserSettings(
@Serializable @Serializable
data class JamBroker( data class JamBroker(
val name: String = "", val name: String = "",
val host: String = "test.mosquitto.org", val host: String = "broker.hivemq.com",
val port: Int = 1883, val port: Int = 8883,
val useTls: Boolean = false, val useTls: Boolean = true,
val username: String? = null, val username: String? = null,
val password: String? = null, val password: String? = null,
val clientIdPrefix: String = "spotube", val clientIdPrefix: String = "spotube",

View File

@ -30,7 +30,7 @@ import platform.WebKit.WKWebViewConfiguration
* Mobile Safari's exact User-Agent on this device. * Mobile Safari's exact User-Agent on this device.
* *
* WKWebView's default UA ends in `Mobile/15E148` without Safari's `Version/x … Safari/604.1`, * WKWebView's default UA ends in `Mobile/15E148` without Safari's `Version/x … Safari/604.1`,
* which sites like accounts.spotify.com treat as an embedded browser ("Oops, something went * which sites like accounts.greedygreencorp.com treat as an embedded browser ("Oops, something went
* wrong"). Hand-building the string isn't safe either: Safari freezes the OS part (iOS 26 * wrong"). Hand-building the string isn't safe either: Safari freezes the OS part (iOS 26
* reports `iPhone OS 18_7`). So WebKit builds it: a probe WKWebView gets Safari's application * reports `iPhone OS 18_7`). So WebKit builds it: a probe WKWebView gets Safari's application
* name and reports `navigator.userAgent`, which is cached for every web view. * name and reports `navigator.userAgent`, which is cached for every web view.

View File

@ -28,6 +28,6 @@ data class ScrobbleTrack(
val trackName: String, val trackName: String,
val artistName: String, val artistName: String,
val albumName: String?, val albumName: String?,
val streamingProvider: String, // e.g. "spotify", "apple_music" etc val streamingProvider: String, // e.g. "greedy_green_corp", "apple_music" etc
val durationMs: Long? = null, val durationMs: Long? = null,
) )